diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..db04eeaa --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/src" + schedule: + interval: "weekly" + day: "saturday" + open-pull-requests-limit: 10 + allow: + - dependency-type: "production" diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 00000000..66b7580a --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,37 @@ +name: "cla" +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +jobs: + cla: + runs-on: ubuntu-latest + steps: + - name: "Get Team Members" + id: team + # github-script, v6.1.0 + uses: actions/github-script@7a5c598405937d486b0331594b5da2b14db670da + with: + github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + result-encoding: string + script: | + const members = await github.paginate( + github.rest.orgs.listMembers, + { org: "skalenetwork" }, + ); + return members.map(m => m.login).join(","); + - name: "CLA Assistant" + if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' + # Beta Release, v2.1.3-beta + uses: cla-assistant/github-action@ba066dbae3769e2ce93ec8cfc4fdc51b9db628ba + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }} + with: + path-to-signatures: 'signatures/version1/cla.json' + path-to-document: 'https://skale.network/cla.txt' + remote-organization-name: 'skalenetwork' + remote-repository-name: cla-sigs + allowlist: '${{ steps.team.outputs.result }},*[bot]' diff --git a/.github/workflows/comprehensive.yml b/.github/workflows/comprehensive.yml new file mode 100644 index 00000000..04fb5259 --- /dev/null +++ b/.github/workflows/comprehensive.yml @@ -0,0 +1,445 @@ + +name: Comprehensive test + +on: + push: + branches-ignore: + - 'docs-v*' + pull_request: + branches-ignore: + - 'docs-v*' + +jobs: + cancel-runs: + name: Cancel Previous Runs + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} + test-comprehensive: + + runs-on: ubuntu-latest + + steps: + + - uses: actions/checkout@v2 + with: + submodules: recursive + + - uses: oven-sh/setup-bun@v1 + + - name: INFO - github environment variable checks + run: | + echo ------------ GIT_CURRENT_BRANCH + export GIT_CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + echo $GIT_CURRENT_BRANCH + echo ------------ GIT_SYMBOLIC_BRANCH + export GIT_SYMBOLIC_BRANCH=$(git symbolic-ref --short HEAD) + echo $GIT_SYMBOLIC_BRANCH + echo ------------ GITHUB_WORKFLOW + echo $GITHUB_WORKFLOW + echo ------------ GITHUB_RUN_ID + echo $GITHUB_RUN_ID + echo ------------ GITHUB_RUN_NUMBER + echo $GITHUB_RUN_NUMBER + echo ------------ GITHUB_ACTION + echo $GITHUB_ACTION + echo ------------ GITHUB_ACTIONS + echo $GITHUB_ACTIONS + echo ------------ GITHUB_ACTOR + echo $GITHUB_ACTOR + echo ------------ GITHUB_REPOSITORY + echo $GITHUB_REPOSITORY + echo ------------ GITHUB_EVENT_NAME + echo $GITHUB_EVENT_NAME + echo ------------ GITHUB_EVENT_PATH + echo $GITHUB_EVENT_PATH + echo ------------ GITHUB_WORKSPACE + echo $GITHUB_WORKSPACE + echo ------------ GITHUB_SHA + echo $GITHUB_SHA + echo ------------ GITHUB_REF + echo $GITHUB_REF + echo ------------ GITHUB_HEAD_REF + echo $GITHUB_HEAD_REF + echo ------------ GITHUB_BASE_REF + echo $GITHUB_BASE_REF + + - name: INFO - user information checks + run: | + echo ------------ user + echo $USER + echo ------------ home + echo $HOME + echo ------------ path + echo $PATH + + - name: INFO - system information checks + run: | + echo ------------ pwd + pwd + echo ------------ unix name - a + uname -a || true + echo ------------ unix name - r + uname -r || true + echo ------------ lsb_release - cat + cat /etc/lsb-release + echo ------------ lsb_release - a + lsb_release -a || true + echo ------------ hostnamectl + hostnamectl || true + echo ------------ /etc/os-release + cat /etc/os-release || true + echo ------------ /proc/version + cat /proc/version || true + echo ------------ lscpu + lscpu || true + + - name: UPDATE - system deps and install libc6, net-tools, btrfs-progs, zip, unzip, bash, procps, curl + run: | + sudo apt-get update + sudo apt-get install -y libc6 net-tools btrfs-progs zip unzip build-essential + sudo apt-get install -y bash + sudo apt-get install -y procps + sudo apt-get install -y curl + + - name: INIT - install docker + run: | + sudo apt-get remove -y docker* containerd* || true + curl -fsSL https://get.docker.com -o install-docker.sh + sudo sh install-docker.sh + docker --version + docker-compose --version + + - name: INIT - reconfigure docker and restart its daemon + run: | + sudo systemctl unmask docker + sudo groupadd docker || true + sudo gpasswd -a username docker || true + sudo service docker restart || true + + - name: INIT - uninstall old Node JS if any + run: | + sudo npm cache clean -f || true &> /dev/null + sudo apt-get remove -y nodejs npm node -y || true &> /dev/null + sudo apt-get purge -y nodejs -y || true &> /dev/null + sudo apt-get autoremove -y || true &> /dev/null + sudo rm -rf /usr/bin/node || true &> /dev/null + sudo rm -rf /usr/include/node || true &> /dev/null + sudo rm -rf /usr/lib/node_modules || true &> /dev/null + sudo rm -rf /usr/local/bin/npm || true &> /dev/null + sudo rm -rf /usr/local/share/man/man1/node* || true &> /dev/null + sudo rm -rf /usr/local/lib/dtrace/node.d || true &> /dev/null + sudo rm -rf ~/.npm || true &> /dev/null + sudo rm -rf ~/.node-gyp || true &> /dev/null + sudo rm -rf /opt/local/bin/node || true &> /dev/null + sudo rm -rf /opt/local/include/node || true &> /dev/null + sudo rm -rf /opt/local/lib/node_modules || true &> /dev/null + sudo rm -rf /usr/local/lib/node* || true &> /dev/null + sudo rm -rf /usr/local/include/node* || true &> /dev/null + sudo rm -rf /usr/local/bin/node* || true &> /dev/null + + - name: INIT - install Node JS + run: | + curl -sL https://deb.nodesource.com/setup_18.x | sudo bash - + sudo apt-get install -y nodejs + sudo ln -s /usr/bin/node /usr/local/bin/node || true + + - name: INIT - install Node utilities + run: | + sudo npm install --global npm + sudo npm install --global color-support + sudo npm install --global yarn + sudo npm install --global node-gyp + + - name: INIT - version checks of Node JS and its utilities + run: | + which node + node --version + which npx + npx --version + which npm + npm --version + which yarn + yarn --version + which node-gyp + node-gyp --version + + - name: INIT - version checks of Bun SH + run: | + which bun + bun --version + + - name: INIT - Download comprehensive-test + working-directory: ${{env.working-directory}} + run: | + export IMA_AGENT_ROOT_DIR=$(pwd) + echo IMA_AGENT_ROOT_DIR = $IMA_AGENT_ROOT_DIR + git clone https://github.com/skalenetwork/comprehensive-test.git --recursive + + - name: INIT - install Ethereum Main Net emulation + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + pwd + cd cli-hh + ./clean.sh + ./init.sh + cd ../.. + + - name: INIT - startup Ethereum Main Net emulation + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd cli-hh + ./run.sh &> ../local_mainnet_output_log.txt & + cd ../.. + + - name: INIT - Install PYTHON + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: INIT - Install everything in IMA + working-directory: ${{env.working-directory}} + run: | + yarn install + + - name: INIT - build IMA + working-directory: ${{env.working-directory}} + run: | + yarn rebuild + + - name: INIT - initialize comprehensive test tokens + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test/test_tokens + yarn install + cd ../.. + + - name: INIT - initialize S-Chain configuration creator + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd s_chain_gen + yarn install + cd .. + cd .. + + - name: INIT - initialize S-Chain configuration creator + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd s_chain_gen + yarn install + cd .. + cd .. + + - name: INIT - check skaled can run + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + export DETECTED_UBUNTU_VERSION=$(lsb_release -r | cut -f2) + export TRYING_SKALED_AT_PATH=./app_cache/bin_$DETECTED_UBUNTU_VERSION/skaled + $TRYING_SKALED_AT_PATH --colors --version + $TRYING_SKALED_AT_PATH --help + cd .. + + - name: INIT - generate configuration files for S-Chain nodes + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd s_chain_gen + ./init.sh + cd ../.. + + - name: INIT - download Skale Manager + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + git clone https://github.com/skalenetwork/skale-manager.git --recursive + cd .. + + - name: INIT - install Skale Manager dependencies + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd skale-manager + yarn install + cd ../.. + + - name: INIT - install comprehensive engine dependencies + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd engine + yarn install + cd ../.. + + - name: SELF-TEST A - use IMA to browse S-Chain via node 00-00 and test last is alive + working-directory: ${{env.working-directory}} + run: | + node ./src/build/main.js --colors --no-gathered --browse-s-chain --url-s-chain=http://127.0.0.1:2164 || true + + - name: SELF-TEST B - use IMA to browse S-Chain via node 00-01 and test last is alive + working-directory: ${{env.working-directory}} + run: | + node ./src/build/main.js --colors --no-gathered --browse-s-chain --url-s-chain=http://127.0.0.2:2264 || true + + - name: SELF-TEST C - use IMA to browse S-Chain via node 01-00 and test last is alive + working-directory: ${{env.working-directory}} + run: | + node ./src/build/main.js --colors --no-gathered --browse-s-chain --url-s-chain=http://127.0.0.3:2364 || true + + - name: SELF-TEST D - use IMA to browse S-Chain via node 01-01 and test last is alive + working-directory: ${{env.working-directory}} + run: | + node ./src/build/main.js --colors --no-gathered --browse-s-chain --url-s-chain=http://127.0.0.4:2464 || true + + - name: INIT - download SGX Wallet + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + git clone https://github.com/skalenetwork/sgxwallet.git --recursive + cd sgxwallet + git checkout develop + git fetch + git pull + git branch + git status + cd .. + cd .. + + - name: INIT - update docker image SGX Wallet in the emulation mode + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd sgxwallet/run_sgx_sim + rm -rf ../../local_sgxwallet_output_log.txt &> /dev/null + echo " --------------------------- stopping sgx wallet ------------------------------------------------------------------------------------------------------ " + docker-compose down + echo " --------------------------- fixing sgx wallets docker config ----------------------------------------------------------------------------------------- " + mv docker-compose.yml docker-compose.yml.old-previous || true + echo "version: '3'" > docker-compose.yml + echo 'services:' >> docker-compose.yml + echo ' sgxwallet:' >> docker-compose.yml + echo ' image: skalenetwork/sgxwallet_sim:1.83.0-develop.19' >> docker-compose.yml + echo ' restart: unless-stopped' >> docker-compose.yml + echo ' ports:' >> docker-compose.yml + echo ' - "1026:1026"' >> docker-compose.yml + echo ' - "1027:1027"' >> docker-compose.yml + echo ' - "1028:1028"' >> docker-compose.yml + echo ' - "1029:1029"' >> docker-compose.yml + echo ' - "1030:1030"' >> docker-compose.yml + echo ' - "1031:1031"' >> docker-compose.yml + echo ' volumes:' >> docker-compose.yml + echo ' - ./sgx_data:/usr/src/sdk/sgx_data' >> docker-compose.yml + echo ' - /dev/urandom:/dev/random' >> docker-compose.yml + echo ' logging:' >> docker-compose.yml + echo ' driver: json-file' >> docker-compose.yml + echo ' options:' >> docker-compose.yml + echo ' max-size: "10m"' >> docker-compose.yml + echo ' max-file: "4"' >> docker-compose.yml + echo ' command: -s -y -V -d' >> docker-compose.yml + echo " --------------------------- pulling sgx wallet ------------------------------------------------------------------------------------------------------- " + docker-compose pull + cd ../.. + cd .. + + - name: INIT - start SGX Wallet + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd sgxwallet/run_sgx_sim + docker-compose up &> ../../local_sgxwallet_output_log.txt & + sleep 90 + cd ../.. + cd .. + + - name: INIT - check SGX Wallet has listening ports + working-directory: ${{env.working-directory}} + run: | + echo "sleeping 45 seconds..." + sleep 45 + echo "checking port 1026 commonly used by SGX Wallet for HTTPS..." + sudo netstat -tulpn | grep 1026 + echo "...Done" + echo "checking port 1027 commonly used by SGX Wallet for HTTP..." + sudo netstat -tulpn | grep 1027 + echo "...Done" + + - name: INIT - download transaction manager + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + rm -rf transaction-manager || true + git clone https://github.com/skalenetwork/transaction-manager --recursive + cd .. + + - name: INIT - start transaction manager and redis + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd transaction-manager + export SGX_SERVER_URL=http://127.0.0.1:1027 + export ENDPOINT=http://127.0.0.1:8545 + export ETH_PRIVATE_KEY=23ABDBD3C61B5330AF61EBE8BEF582F4E5CC08E554053A718BDCE7813B9DC1FC + ./scripts/run-test-containers.sh + cd .. + echo "------------------------------------------------------------------------------------" + docker ps + echo "------------------------------------------------------------------------------------" + export TM_CONTAINER_ID=$(docker ps | grep transaction-manager | awk '{print $1;}') + echo "Transaction manager docker container ID:" $TM_CONTAINER_ID + docker logs $TM_CONTAINER_ID -f &> engine/tm.log & + echo "------------------------------------------------------------------------------------" + export REDIS_CONTAINER_ID=$(docker ps | grep redis | awk '{print $1;}') + echo "Redis container ID:" $REDIS_CONTAINER_ID + docker logs $REDIS_CONTAINER_ID -f &> engine/redis.log & + cd .. + + - name: INIT - prepare ulimit + working-directory: ${{env.working-directory}} + run: | + ulimit -n 65535 > /dev/null + echo "ulimit is now set to" $(ulimit -n) + + - name: RUN - create certificates + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd engine/create_pems + ./create_pems.sh + cd ../.. + cd .. + + - name: RUN - main engine steps + working-directory: ${{env.working-directory}} + run: | + export IMA_AGENT_ROOT_DIR=$(pwd) + echo IMA_AGENT_ROOT_DIR = $IMA_AGENT_ROOT_DIR + cd comprehensive-test/engine + export ALL_SKALE_TEST_CLOUD_RUN=1 + export SEPARATED_IMA_AGENT_MODE=1 + node ./index.js + cd ../.. + + - name: SHUTDOWN - stop SGX Wallet + run: | + cd comprehensive-test + cd sgxwallet/run_sgx_sim + docker-compose down + cd ../.. + cd .. + + - name: SHUTDOWN - stop transaction manager and redis + run: | + docker stop $TM_CONTAINER_ID $REDIS_CONTAINER_ID || true + docker rm $TM_CONTAINER_ID $REDIS_CONTAINER_ID || true + + - name: SHUTDOWN - zombie cleanup, if any + run: | + killall -9 skaled node npx python python3 || true + pkill -9 -f skaled || true diff --git a/.github/workflows/container-test.yml b/.github/workflows/container-test.yml new file mode 100644 index 00000000..9d94445b --- /dev/null +++ b/.github/workflows/container-test.yml @@ -0,0 +1,493 @@ + +name: IMA container test + +on: + push: + branches-ignore: + - 'docs-v*' + pull_request: + branches-ignore: + - 'docs-v*' + +jobs: + cancel-runs: + name: Cancel Previous Runs + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} + test-comprehensive: + + runs-on: self-hosted + + steps: + + - uses: oven-sh/setup-bun@v1 + + - name: INIT - force pre-clean + run: | + echo "------------------------------------------------------------------------------------" + sudo docker ps + echo "------------------------------------------------------------------------------------" + sudo docker stop run_sgx_sim_sgxwallet_1 || true + sudo docker stop tm || true + sudo docker stop redis || true + echo "------------------------------------------------------------------------------------" + sudo docker ps + echo "------------------------------------------------------------------------------------" + sudo rm -rf comprehensive-test || true + sudo rm -rf ./sgxwallet || true + sudo rm -rf ./transaction-manager || true + + - uses: actions/checkout@v2 + with: + submodules: recursive + + - uses: oven-sh/setup-bun@v1 + + - name: INFO - github environment variable checks + run: | + echo ------------ GIT_CURRENT_BRANCH + export GIT_CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + echo $GIT_CURRENT_BRANCH + echo ------------ GIT_SYMBOLIC_BRANCH + export GIT_SYMBOLIC_BRANCH=$(git symbolic-ref --short HEAD) + echo $GIT_SYMBOLIC_BRANCH + echo ------------ GITHUB_WORKFLOW + echo $GITHUB_WORKFLOW + echo ------------ GITHUB_RUN_ID + echo $GITHUB_RUN_ID + echo ------------ GITHUB_RUN_NUMBER + echo $GITHUB_RUN_NUMBER + echo ------------ GITHUB_ACTION + echo $GITHUB_ACTION + echo ------------ GITHUB_ACTIONS + echo $GITHUB_ACTIONS + echo ------------ GITHUB_ACTOR + echo $GITHUB_ACTOR + echo ------------ GITHUB_REPOSITORY + echo $GITHUB_REPOSITORY + echo ------------ GITHUB_EVENT_NAME + echo $GITHUB_EVENT_NAME + echo ------------ GITHUB_EVENT_PATH + echo $GITHUB_EVENT_PATH + echo ------------ GITHUB_WORKSPACE + echo $GITHUB_WORKSPACE + echo ------------ GITHUB_SHA + echo $GITHUB_SHA + echo ------------ GITHUB_REF + echo $GITHUB_REF + echo ------------ GITHUB_HEAD_REF + echo $GITHUB_HEAD_REF + echo ------------ GITHUB_BASE_REF + echo $GITHUB_BASE_REF + + - name: INFO - user information checks + run: | + echo ------------ user + echo $USER + echo ------------ home + echo $HOME + echo ------------ path + echo $PATH + + - name: INFO - system information checks + run: | + echo ------------ pwd + pwd + echo ------------ unix name - a + uname -a || true + echo ------------ unix name - r + uname -r || true + echo ------------ lsb_release - cat + cat /etc/lsb-release + echo ------------ lsb_release - a + lsb_release -a || true + echo ------------ hostnamectl + hostnamectl || true + echo ------------ /etc/os-release + cat /etc/os-release || true + echo ------------ /proc/version + cat /proc/version || true + echo ------------ lscpu + lscpu || true + + - name: UPDATE - system deps and install libc6, net-tools, btrfs-progs, zip, unzip, bash, procps, curl, jq + run: | + sudo apt-get update + sudo apt-get install -y libc6 net-tools btrfs-progs zip unzip build-essential + sudo apt-get install -y bash + sudo apt-get install -y procps + sudo apt-get install -y curl + sudo apt-get install -y jq + + - name: INIT - uninstall old Node JS if any + run: | + sudo npm cache clean -f || true &> /dev/null + sudo apt-get remove -y nodejs npm node -y || true &> /dev/null + sudo apt-get purge -y nodejs -y || true &> /dev/null + sudo apt-get autoremove -y || true &> /dev/null + sudo rm -rf /usr/bin/node || true &> /dev/null + sudo rm -rf /usr/include/node || true &> /dev/null + sudo rm -rf /usr/lib/node_modules || true &> /dev/null + sudo rm -rf /usr/local/bin/npm || true &> /dev/null + sudo rm -rf /usr/local/share/man/man1/node* || true &> /dev/null + sudo rm -rf /usr/local/lib/dtrace/node.d || true &> /dev/null + sudo rm -rf ~/.npm || true &> /dev/null + sudo rm -rf ~/.node-gyp || true &> /dev/null + sudo rm -rf /opt/local/bin/node || true &> /dev/null + sudo rm -rf /opt/local/include/node || true &> /dev/null + sudo rm -rf /opt/local/lib/node_modules || true &> /dev/null + sudo rm -rf /usr/local/lib/node* || true &> /dev/null + sudo rm -rf /usr/local/include/node* || true &> /dev/null + sudo rm -rf /usr/local/bin/node* || true &> /dev/null + + - name: INIT - install Node JS + run: | + curl -sL https://deb.nodesource.com/setup_18.x | sudo bash - + sudo apt-get install -y nodejs + sudo ln -s /usr/bin/node /usr/local/bin/node || true + + - name: INIT - install Node utilities + run: | + sudo npm install --global npm + sudo npm install --global color-support + sudo npm install --global yarn + sudo npm install --global node-gyp + + - name: INIT - version checks of Node JS and its utilities + run: | + which node + node --version + which npx + npx --version + which npm + npm --version + which yarn + yarn --version + which node-gyp + node-gyp --version + + - name: INIT - version checks of Bun SH + run: | + which bun + bun --version + + - name: INIT - Download comprehensive-test + working-directory: ${{env.working-directory}} + run: | + sudo rm -rf ./comprehensive-test || true + export IMA_AGENT_ROOT_DIR=$(pwd) + echo IMA_AGENT_ROOT_DIR = $IMA_AGENT_ROOT_DIR + git clone https://github.com/skalenetwork/comprehensive-test.git --recursive + + - name: INIT - install Ethereum Main Net emulation + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + pwd + cd cli-hh + ./clean.sh + ./init.sh + cd ../.. + + - name: INIT - startup Ethereum Main Net emulation + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd cli-hh + ./run.sh &> ../local_mainnet_output_log.txt & + cd ../.. + + - name: INIT - Install PYTHON + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: INIT - Install venv for PYTHON + working-directory: ${{env.working-directory}} + run: | + sudo apt-get install -y python3.8-venv + + - name: INIT - Install everything in IMA + working-directory: ${{env.working-directory}} + run: | + yarn install + + - name: INIT - build IMA + working-directory: ${{env.working-directory}} + run: | + yarn rebuild + + - name: INIT - initialize comprehensive test tokens + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test/test_tokens + yarn install + cd ../.. + + - name: INIT - initialize S-Chain configuration creator + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd s_chain_gen + yarn install + cd .. + cd .. + + - name: INIT - initialize S-Chain configuration creator + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd s_chain_gen + yarn install + cd .. + cd .. + + - name: INIT - check skaled can run + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + export DETECTED_UBUNTU_VERSION=$(lsb_release -r | cut -f2) + export TRYING_SKALED_AT_PATH=./app_cache/bin_$DETECTED_UBUNTU_VERSION/skaled + $TRYING_SKALED_AT_PATH --colors --version + $TRYING_SKALED_AT_PATH --help + cd .. + + - name: INIT - generate configuration files for S-Chain nodes + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd s_chain_gen + ./init.sh + cd ../.. + + - name: INIT - download Skale Manager + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + sudo rm -rf ./skale-manager || true + git clone https://github.com/skalenetwork/skale-manager.git --recursive + cd .. + + - name: INIT - install Skale Manager dependencies + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd skale-manager + yarn install + cd ../.. + + - name: INIT - install comprehensive engine dependencies + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd engine + yarn install + cd ../.. + + - name: SELF-TEST A - use IMA to browse S-Chain via node 00-00 and test last is alive + working-directory: ${{env.working-directory}} + run: | + node ./src/build/main.js --colors --no-gathered --browse-s-chain --url-s-chain=http://127.0.0.1:2164 || true + + - name: SELF-TEST B - use IMA to browse S-Chain via node 00-01 and test last is alive + working-directory: ${{env.working-directory}} + run: | + node ./src/build/main.js --colors --no-gathered --browse-s-chain --url-s-chain=http://127.0.0.2:2264 || true + + - name: SELF-TEST C - use IMA to browse S-Chain via node 01-00 and test last is alive + working-directory: ${{env.working-directory}} + run: | + node ./src/build/main.js --colors --no-gathered --browse-s-chain --url-s-chain=http://127.0.0.3:2364 || true + + - name: SELF-TEST D - use IMA to browse S-Chain via node 01-01 and test last is alive + working-directory: ${{env.working-directory}} + run: | + node ./src/build/main.js --colors --no-gathered --browse-s-chain --url-s-chain=http://127.0.0.4:2464 || true + + - name: INIT - build IMA docker container + working-directory: ${{env.working-directory}} + run: | + echo ------------ download binaries + LIB_BLS_RELEASE_TAG="0.2.0-develop.3" bash ./scripts/download_binaries.sh + echo ------------ docker image + export RELEASE=false + export VERSION=13.13.13-test.13 + echo "Docker image(will build) version is:" $VERSION + export NAME=ima + export BRANCH=$GIT_CURRENT_BRANCH + export REPO_NAME=skalenetwork/$NAME + export IMAGE_NAME=$REPO_NAME:$VERSION + export LATEST_IMAGE_NAME=$REPO_NAME:latest + echo "Docker image(will build) name for IMA Agent is:" $IMAGE_NAME + bash ./scripts/build_image.sh "" "" + + - name: INIT - download SGX Wallet + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + sudo docker stop run_sgx_sim_sgxwallet_1 || true + sudo rm -rf ./sgxwallet || true + git clone https://github.com/skalenetwork/sgxwallet.git --recursive + cd sgxwallet + git checkout develop + git fetch + git pull + git branch + git status + cd .. + cd .. + + - name: INIT - update docker image SGX Wallet in the emulation mode + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd sgxwallet/run_sgx_sim + rm -rf ../../local_sgxwallet_output_log.txt &> /dev/null + echo " --------------------------- stopping sgx wallet ------------------------------------------------------------------------------------------------------ " + docker-compose down + echo " --------------------------- fixing sgx wallets docker config ----------------------------------------------------------------------------------------- " + mv docker-compose.yml docker-compose.yml.old-previous || true + echo "version: '3'" > docker-compose.yml + echo 'services:' >> docker-compose.yml + echo ' sgxwallet:' >> docker-compose.yml + echo ' image: skalenetwork/sgxwallet_sim:1.9.0-develop.11' >> docker-compose.yml + echo ' restart: unless-stopped' >> docker-compose.yml + echo ' network_mode: "host"' >> docker-compose.yml + # echo ' ports:' >> docker-compose.yml + # echo ' - "1026:1026"' >> docker-compose.yml + # echo ' - "1027:1027"' >> docker-compose.yml + # echo ' - "1028:1028"' >> docker-compose.yml + # echo ' - "1029:1029"' >> docker-compose.yml + # echo ' - "1030:1030"' >> docker-compose.yml + # echo ' - "1031:1031"' >> docker-compose.yml + echo ' volumes:' >> docker-compose.yml + echo ' - ./sgx_data:/usr/src/sdk/sgx_data' >> docker-compose.yml + echo ' - /dev/urandom:/dev/random' >> docker-compose.yml + echo ' logging:' >> docker-compose.yml + echo ' driver: json-file' >> docker-compose.yml + echo ' options:' >> docker-compose.yml + echo ' max-size: "10m"' >> docker-compose.yml + echo ' max-file: "4"' >> docker-compose.yml + echo ' command: -s -y -V -d' >> docker-compose.yml + echo " --------------------------- pulling sgx wallet ------------------------------------------------------------------------------------------------------- " + docker-compose pull + cd ../.. + cd .. + + - name: INIT - start SGX Wallet + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd sgxwallet/run_sgx_sim + docker-compose up &> ../../local_sgxwallet_output_log.txt & + sleep 90 + cd ../.. + cd .. + + - name: INIT - check SGX Wallet has listening ports + working-directory: ${{env.working-directory}} + run: | + echo "sleeping 180 seconds..." + sleep 180 + echo "checking port 1026 commonly used by SGX Wallet for HTTPS..." + sudo netstat -tulpn | grep 1026 + echo "...Done" + echo "checking port 1027 commonly used by SGX Wallet for HTTP..." + sudo netstat -tulpn | grep 1027 + echo "...Done" + + - name: INIT - download transaction manager + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + sudo rm -rf ./transaction-manager || true + git clone https://github.com/skalenetwork/transaction-manager --recursive + cd .. + + - name: INIT - start transaction manager and redis + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd transaction-manager + export SGX_SERVER_URL=http://127.0.0.1:1027 + export ENDPOINT=http://127.0.0.1:8545 + #export ETH_PRIVATE_KEY=23ABDBD3C61B5330AF61EBE8BEF582F4E5CC08E554053A718BDCE7813B9DC1FC + export ETH_PRIVATE_KEY=fd6d151c4afe5c1c856e5a234950252be7a90210f3aa2bad4e0db037355c1dd6 + ./scripts/run-test-containers.sh + cd .. + echo "------------------------------------------------------------------------------------" + docker ps + echo "------------------------------------------------------------------------------------" + export TM_CONTAINER_ID=$(docker ps | grep transaction-manager | awk '{print $1;}') + echo "Transaction manager docker container ID:" $TM_CONTAINER_ID + docker logs $TM_CONTAINER_ID -f &> engine/tm.log & + echo "------------------------------------------------------------------------------------" + export REDIS_CONTAINER_ID=$(docker ps | grep redis | awk '{print $1;}') + echo "Redis container ID:" $REDIS_CONTAINER_ID + docker logs $REDIS_CONTAINER_ID -f &> engine/redis.log & + cd .. + + - name: INIT - prepare ulimit + working-directory: ${{env.working-directory}} + run: | + ulimit -n 65535 > /dev/null + echo "ulimit is now set to" $(ulimit -n) + + - name: RUN - create certificates + working-directory: ${{env.working-directory}} + run: | + cd comprehensive-test + cd engine/create_pems + sudo rm -rf ./new_certs/* || true + sudo rm -rf ./k.key || true + sudo rm -rf ./k.pem || true + sudo rm -rf ./k.crt || true + sudo rm -rf ./client.key || true + sudo rm -rf ./client.pem || true + sudo rm -rf ./client.crt || true + ./create_pems.sh + cd ../.. + cd .. + + - name: RUN - main engine steps + working-directory: ${{env.working-directory}} + run: | + export IMA_AGENT_ROOT_DIR=$(pwd) + echo IMA_AGENT_ROOT_DIR = $IMA_AGENT_ROOT_DIR + cd comprehensive-test/engine + export ALL_SKALE_TEST_CLOUD_RUN=1 + # + export VERSION=13.13.13-test.13 + echo "Docker image(will run) version is:" $VERSION + export NAME=ima + export BRANCH=$GIT_CURRENT_BRANCH + export REPO_NAME=skalenetwork/$NAME + export IMAGE_NAME=$REPO_NAME:$VERSION + export LATEST_IMAGE_NAME=$REPO_NAME:latest + echo "Docker image(will run) name for IMA Agent is:" $IMAGE_NAME + # + export SEPARATED_IMA_AGENT_MODE=1 + node ./index.js + cd ../.. + + - name: SHUTDOWN - stop SGX Wallet + run: | + cd comprehensive-test + cd sgxwallet/run_sgx_sim + docker-compose down + cd ../.. + cd .. + + - name: SHUTDOWN - stop transaction manager and redis + run: | + docker stop $TM_CONTAINER_ID $REDIS_CONTAINER_ID || true + docker rm $TM_CONTAINER_ID $REDIS_CONTAINER_ID || true + + - name: SHUTDOWN - zombie cleanup, if any + run: | + killall -9 skaled node npx python python3 || true + pkill -9 -f skaled || true diff --git a/.github/workflows/issue_check.yml b/.github/workflows/issue_check.yml new file mode 100644 index 00000000..ee5ec6b2 --- /dev/null +++ b/.github/workflows/issue_check.yml @@ -0,0 +1,19 @@ +name: Get linked issues +on: + pull_request: + types: [ edited, synchronize, opened, reopened ] + +jobs: + check-linked-issues: + name: Check if pull request has linked issues + runs-on: ubuntu-latest + steps: + - name: Get issues + id: get-issues + uses: mondeja/pr-linked-issues-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + - name: PR has not linked issues + if: join(steps.get-issues.outputs.issues) == '' + run: + exit 1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..eaca4402 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,292 @@ +# This is a basic workflow to help you get started with Actions + +name: Build and test + +on: + push: + branches-ignore: + - 'docs-v*' + pull_request: + branches-ignore: + - 'docs-v*' + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + test-agent: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + with: + submodules: recursive + + - uses: oven-sh/setup-bun@v1 + + - name: System Version Checks + run: | + echo ------------ GIT_CURRENT_BRANCH + export GIT_CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + echo $GIT_CURRENT_BRANCH + echo ------------ GIT_SYMBOLIC_BRANCH + export GIT_SYMBOLIC_BRANCH=$(git symbolic-ref --short HEAD) + echo $GIT_SYMBOLIC_BRANCH + echo ------------ GITHUB_WORKFLOW + echo $GITHUB_WORKFLOW + echo ------------ GITHUB_RUN_ID + echo $GITHUB_RUN_ID + echo ------------ GITHUB_RUN_NUMBER + echo $GITHUB_RUN_NUMBER + echo ------------ GITHUB_ACTION + echo $GITHUB_ACTION + echo ------------ GITHUB_ACTIONS + echo $GITHUB_ACTIONS + echo ------------ GITHUB_ACTOR + echo $GITHUB_ACTOR + echo ------------ GITHUB_REPOSITORY + echo $GITHUB_REPOSITORY + echo ------------ GITHUB_EVENT_NAME + echo $GITHUB_EVENT_NAME + echo ------------ GITHUB_EVENT_PATH + echo $GITHUB_EVENT_PATH + echo ------------ GITHUB_WORKSPACE + echo $GITHUB_WORKSPACE + echo ------------ GITHUB_SHA + echo $GITHUB_SHA + echo ------------ GITHUB_REF + echo $GITHUB_REF + echo ------------ GITHUB_HEAD_REF + echo $GITHUB_HEAD_REF + echo ------------ GITHUB_BASE_REF + echo $GITHUB_BASE_REF + echo ------------ user + echo $USER + echo ------------ home + echo $HOME + echo ------------ path + echo $PATH + echo ------------ pwd + pwd + echo ------------ unix name - a + uname -a || true + echo ------------ unix name - r + uname -r || true + echo ------------ lsb + lsb_release -a || true + echo ------------ hostnamectl + hostnamectl || true + echo ------------ /etc/os-release + cat /etc/os-release || true + echo ------------ /proc/version + cat /proc/version || true + echo ------------ lscpu + lscpu || true + + - name: INIT - install needed utilities + run: | + sudo apt-get install -y jq sed + + - name: INIT - install Node JS + run: | + curl -sL https://deb.nodesource.com/setup_18.x | sudo bash - + sudo apt-get install -y nodejs + sudo ln -s /usr/bin/node /usr/local/bin/node || true + + - name: INIT - install Node utilities + run: | + sudo npm install --global npm + sudo npm install --global color-support + sudo npm install --global yarn + sudo npm install --global node-gyp + + - name: INIT - version checks of Node JS and its utilities + run: | + which node + node --version + which npx + npx --version + which npm + npm --version + which yarn + yarn --version + which node-gyp + node-gyp --version + + - name: INIT - version checks of Bun SH + run: | + which bun + bun --version + + - name: Install ESLINT + run: | + sudo npm install -g eslint + eslint --version + + - name: Install all NPMs at root + run: | + yarn install + ls -1 + + - name: JS Lint Check src + run: | + yarn run lint-check + echo "Looks like no JS code formatting errors so far)" + + - name: JS Lint Check network-browser + run: | + yarn run lint-nb + + - name: Python Version Checks + run: | + echo ------------ python version check + which python || echo "----> Looks like python was not installed, next command will fail" + python --version + echo ------------ python3 version check + which python3 || echo "----> Looks like python3 was not installed, next command will fail" + python3 --version + + - name: Install Python Prerequisites + run: | + echo ------------ py3 installs + sudo apt-get install -y python3-pip python3-setuptools python3-dev + echo ------------ py3 wheel - apt + sudo apt-get install -y python3-wheel + echo ------------ py3 wheel - pip + pip3 install wheel + # echo ------------ slither analyzer install + # #pip3 install slither-analyzer==0.8.3 + # pip3 install -r IMA/proxy/scripts/requirements.txt + # echo ------------ slither search attempt + # sudo find / -name slither || true + # echo ------------ slither location detection - after install + # export PATH=$PATH:/home/$USER/.local/bin + # which slither || echo "----> Looks like slither was not installed, next command will fail" + # echo ------------ slither version check - after install + # slither --version || true + + test-integration: + runs-on: ubuntu-latest + + env: + working-directory: ./test + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + + - uses: oven-sh/setup-bun@v1 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: INIT - install Node JS + run: | + curl -sL https://deb.nodesource.com/setup_18.x | sudo bash - + sudo apt-get install -y nodejs + sudo ln -s /usr/bin/node /usr/local/bin/node || true + + - name: INIT - install Node utilities + run: | + sudo npm install --global npm + sudo npm install --global color-support + sudo npm install --global yarn + sudo npm install --global node-gyp + + - name: INIT - version checks of Node JS and its utilities + run: | + which node + node --version + which npx + npx --version + which npm + npm --version + which yarn + yarn --version + which node-gyp + node-gyp --version + + - name: INIT - version checks of Bun SH + run: | + which bun + bun --version + + - name: Install Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install all NPMs in src + working-directory: ./src + run: | + yarn install + + - name: INIT - build IMA + working-directory: ./src + run: | + yarn rebuild + + - name: Compile contracts + working-directory: ./IMA/proxy + run: | + yarn install + + - name: Install all NPMs in test + working-directory: ./test + run: | + yarn install + + - name: Start background ganache + working-directory: ./IMA/proxy + run: | + npx ganache --miner.blockGasLimit 12000000 --logging.quiet --chain.allowUnlimitedContractSize --wallet.defaultBalance 2000000 --wallet.accountKeysPath ../../test/accounts.json & + sleep 3 + echo "Content of \"accounts.json\" is:" + cat ../../test/accounts.json | jq + + - name: Prepare test (PY part) + working-directory: ./test + run: | + python3 ../scripts/config_from_accounts.py accounts.json config.json + echo "Content of \"config.json\" is:" + cat config.json | jq + export PRIVATE_KEY_FOR_ETHEREUM=$( cat config.json | jq -M .PRIVATE_KEY_FOR_ETHEREUM | tr -d '"' | sed -e "s/^0x//" ) + echo "Value of \"PRIVATE_KEY_FOR_ETHEREUM\" is" $PRIVATE_KEY_FOR_ETHEREUM + export PRIVATE_KEY_FOR_SCHAIN=$( cat config.json | jq -M .PRIVATE_KEY_FOR_SCHAIN | tr -d '"' | sed -e "s/^0x//" ) + echo "Value of \"PRIVATE_KEY_FOR_SCHAIN\" is" $PRIVATE_KEY_FOR_SCHAIN + pip3 install -r requirements.txt + + - name: Run test (PY part) + working-directory: ./test + run: | + export URL_W3_ETHEREUM="http://127.0.0.1:8545" + export URL_W3_S_CHAIN="http://127.0.0.1:8545" + python3 test.py + + - name: Prepare test (JS part) + working-directory: ./test + run: | + yarn install + + - name: Run test (JS part) + working-directory: ./test + run: | + yarn test diff --git a/.github/workflows/network-browser-test.yml b/.github/workflows/network-browser-test.yml new file mode 100644 index 00000000..96da99a1 --- /dev/null +++ b/.github/workflows/network-browser-test.yml @@ -0,0 +1,28 @@ +name: network-browser test +on: [push, pull_request] +env: + ETH_PRIVATE_KEY: ${{ secrets.ETH_PRIVATE_KEY }} + MANAGER_TAG: "1.9.3-beta.0" +jobs: + test_network_browser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: oven-sh/setup-bun@v1 + - name: Launch hardhat node + working-directory: hardhat-node + run: docker-compose up -d + - name: Deploy manager contracts + run: | + bash ./helper-scripts/deploy_test_manager.sh + docker rmi -f skalenetwork/skale-manager:${{ env.MANAGER_TAG }} + + - name: Install network-browser dependencies + working-directory: network-browser + run: bun i + + - name: Run network-browser tests + working-directory: network-browser + run: bash run_tests.sh \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..0b906e79 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,97 @@ +name: Build and publish IMA Agent + +on: + pull_request: + types: [closed] + branches: + - 'v*.*.*' + - develop + - beta + - stable + push: + tags: + - 'custom-release-*' + +jobs: + build: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - uses: oven-sh/setup-bun@v1 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: INIT - install Node JS + run: | + curl -sL https://deb.nodesource.com/setup_18.x | sudo bash - + sudo apt-get install -y nodejs + sudo ln -s /usr/bin/node /usr/local/bin/node || true + + - name: INIT - install Node utilities + run: | + sudo npm install --global npm + sudo npm install --global color-support + sudo npm install --global yarn + sudo npm install --global node-gyp + + - name: INIT - version checks of Node JS and its utilities + run: | + which node + node --version + which npx + npx --version + which npm + npm --version + which yarn + yarn --version + which node-gyp + node-gyp --version + + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Build and publish container + run: | + export BRANCH=${GITHUB_REF##*/} + echo "Branch $BRANCH" + export VERSION=$(bash ./scripts/calculate_version.sh) + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "Version $VERSION" + ( test $BRANCH = "stable" && export PRERELEASE=false ) || export PRERELEASE=true + echo "PRERELEASE=$PRERELEASE" >> $GITHUB_ENV + echo ------------ download binaries + LIB_BLS_RELEASE_TAG=${{ secrets.LIB_BLS_RELEASE_TAG }} bash ./scripts/download_binaries.sh + export RELEASE=true + echo "RELEASE=$RELEASE" >> $GITHUB_ENV + echo ------------ docker image + bash ./scripts/build_image.sh ${{ secrets.DOCKER_USERNAME }} ${{ secrets.DOCKER_PASSWORD }} #|| echo "----> Looks like deploy failed" + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.VERSION }} + release_name: ${{ env.VERSION }} + draft: false + prerelease: ${{ env.PRERELEASE }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..cd376f14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# idea +.DS_Store +.idea + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock +*.egg-info + +# nyc test coverage +.nyc_output + +# Dependency directories +node_modules +src/node_modules +src/test/node_modules +test/node_modules +test-tokens/node_modules + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +wallet*.json +*_ktm_info.tar.gz + +__pycache__/ +**/*.bak + +ima_data.json + +test/working +test/config.json +test/accounts.json + +.vscode + +actions-runner/ + +bls_glue +hash_g1 +verify_bls + +venv + +# network-browser + +network-browser/test*.json + +src/build +network-browser/build/* +network-browser.js diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..99e85c7e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "IMA"] + path = IMA + url = https://github.com/skalenetwork/IMA.git +[submodule "hardhat-node"] + path = hardhat-node + url = https://github.com/skalenetwork/hardhat-node.git +[submodule "helper-scripts"] + path = helper-scripts + url = https://github.com/skalenetwork/helper-scripts.git diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..f2953899 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +* @sergiy-skalelabs @kladkogex @DmytroNazarenko +*.md @skalenetwork/docowners diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..1ece5042 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,72 @@ +FROM ubuntu:jammy + +RUN apt-get update +RUN apt-get install --no-install-recommends -yq software-properties-common +RUN apt-get update +RUN apt-get install --no-install-recommends -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev wget curl sudo git + +# NOTICE: we need to install SSL 1.1 manually here in order to make BLS command line tools working +RUN echo "deb http://security.ubuntu.com/ubuntu focal-security main" | tee /etc/apt/sources.list.d/focal-security.list +RUN apt-get update +RUN apt-get install --no-install-recommends -y unzip curl wget +# NOTICE: to remove extra dep above: sudo rm /etc/apt/sources.list.d/focal-security.list + +RUN wget https://www.openssl.org/source/old/1.1.0/openssl-1.1.0l.tar.gz +RUN tar xfz openssl-1.1.0l.tar.gz +RUN cd openssl-1.1.0l && ./config && make && make install && cd .. +RUN ldconfig + +RUN curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr bash -s "bun-v1.0.16" +RUN bun --version + +RUN curl -sL https://deb.nodesource.com/setup_18.x | bash +RUN apt-get install --no-install-recommends -y nodejs +RUN npm install npm --global +RUN npm install --global yarn +RUN npm --version +RUN yarn --version + +RUN curl -O https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tar.xz +RUN tar -xf Python-3.7.3.tar.xz +RUN cd Python-3.7.3; ./configure --enable-optimizations; make -j 4 build_all; make altinstall; cd .. +RUN python3.7 --version +RUN which python3.7 +RUN rm -f /usr/bin/python3 +RUN ln -s /usr/local/bin/python3.7 /usr/bin/python3 +RUN python3 --version +RUN which python3 + +RUN mkdir /ima +WORKDIR /ima + +COPY package.json package.json + +COPY runner runner +COPY src src +RUN mkdir IMA +COPY IMA/proxy IMA/proxy +COPY IMA/package.json IMA/package.json +COPY IMA/postinstall.sh IMA/postinstall.sh +COPY IMA/VERSION IMA/VERSION +COPY package.json package.json +COPY VERSION VERSION + +COPY network-browser network-browser +RUN cd network-browser && bun install && bun build:rollup + +RUN mkdir /ima/bls_binaries +COPY scripts/bls_binaries /ima/bls_binaries + +RUN chmod +x /ima/bls_binaries/bls_glue +RUN chmod +x /ima/bls_binaries/hash_g1 +RUN chmod +x /ima/bls_binaries/verify_bls + +RUN npm install -g node-gyp +RUN which node-gyp +RUN node-gyp --version + +WORKDIR /ima +RUN yarn install +RUN yarn rebuild + +CMD ["bash", "/ima/runner/run.sh"] diff --git a/IMA b/IMA new file mode 160000 index 00000000..d4c05181 --- /dev/null +++ b/IMA @@ -0,0 +1 @@ +Subproject commit d4c051814e89ca243933794ef1fc01d3c2118b93 diff --git a/README.md b/README.md index cab5df7f..6ecb56b4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,49 @@ -# ima-agent -Containerized SKALE Interchain Messaging Agent + + +# SKALE Interchain Messaging Agent (IMA) + +[![Discord](https://img.shields.io/discord/534485763354787851.svg)](https://discord.gg/vvUtWJB) + +## Components Structure + +### Message Transferring Agent App + +IMA Agent is the main application implementing connectivity and message transfer between `Mainnet` and `SKALE Chains`. The Agent also provides an easy way to perform ETH, ERC20, and ERC721 transfers between `Main Net` and `S-Chain` nevertheless, this can be done without it. + +#### Core + +A module implementing core IMA functionality. + +#### OWASP + +Data validity verifier module. See [OWASP document](https://www.gitbook.com/download/pdf/book/checkmarx/JS-SCP). + +#### SKALE Observer + +[SKALE Network Browser](src/SNB.md). Responsible for providing description of all SKALE chains. + +#### IMA Log + +Console and log file output with rotation. + +#### IMA CC + +ANSI colorizer for console and log output. + +## For more information + +- [SKALE Network Website](https://skale.network) +- [SKALE Network Twitter](https://twitter.com/SkaleNetwork) +- [SKALE Network Blog](https://skale.network/blog) + +Learn more about the SKALE community over on [Discord](https://discord.gg/vvUtWJB). + +## Security and Liability + +All contracts are WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +## License + +[![License](https://img.shields.io/github/license/skalenetwork/IMA)](LICENSE) +All contributions are made under the [GNU Affero General Public License v3](https://www.gnu.org/licenses/agpl-3.0.en.html). See [LICENSE](LICENSE). +Copyright (C) 2019-Present SKALE Labs. diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..7ec1d6db --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2.1.0 diff --git a/hardhat-node b/hardhat-node new file mode 160000 index 00000000..8a4b03fd --- /dev/null +++ b/hardhat-node @@ -0,0 +1 @@ +Subproject commit 8a4b03fd1051960a3e0182280bf4bfdc43129997 diff --git a/helper-scripts b/helper-scripts new file mode 160000 index 00000000..34adbed6 --- /dev/null +++ b/helper-scripts @@ -0,0 +1 @@ +Subproject commit 34adbed6050a23aec351eb90501b2ac2846c0f4b diff --git a/network-browser/.env-template b/network-browser/.env-template new file mode 100644 index 00000000..3ee04298 --- /dev/null +++ b/network-browser/.env-template @@ -0,0 +1,18 @@ +# required + +MAINNET_RPC_URL= +SCHAIN_RPC_URL= +SCHAIN_NAME= + +SCHAIN_PROXY_PATH= +MANAGER_ABI_PATH= +IMA_NETWORK_BROWSER_DATA_PATH= + +# optional + +MULTICALL=false +CONNECTED_ONLY=true + +POST_ERROR_DELAY=5 +NETWORK_BROWSER_DELAY=10800 +NETWORK_BROWSER_TIMEOUT=1200 \ No newline at end of file diff --git a/network-browser/.eslintrc.cjs b/network-browser/.eslintrc.cjs new file mode 100644 index 00000000..8fbed79b --- /dev/null +++ b/network-browser/.eslintrc.cjs @@ -0,0 +1,30 @@ +module.exports = { + env: { + browser: true, + es2022: true, + node: true + }, + extends: 'standard-with-typescript', + overrides: [ + { + env: { + node: true + }, + files: ['.eslintrc.{js,cjs}'], + parserOptions: { + sourceType: 'script' + } + } + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['tsconfig.json'] + }, + rules: { + '@typescript-eslint/indent': ['error', 4], + '@typescript-eslint/quotes': ['error', 'single'], + '@typescript-eslint/space-before-function-paren': 0, + 'no-unused-vars': 2 + } +} diff --git a/network-browser/.prettierrc b/network-browser/.prettierrc new file mode 100644 index 00000000..4b014aad --- /dev/null +++ b/network-browser/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": false, + "trailingComma": "none", + "singleQuote": true, + "printWidth": 100, + "endOfLine": "auto", + "bracketSpacing": true, + "tabWidth": 4 +} \ No newline at end of file diff --git a/network-browser/README.md b/network-browser/README.md new file mode 100644 index 00000000..98c28abd --- /dev/null +++ b/network-browser/README.md @@ -0,0 +1,62 @@ +# SKALE Network Browser + +## Configuration + +Required env variables: + +- `MAINNET_RPC_URL` - endpoint of the Mainnet network where skale-manager contracts are deployed +- `SCHAIN_RPC_URL` - endpoint of the current sChain +- `SCHAIN_NAME` - name of the current sChain + +- `SCHAIN_PROXY_PATH` - IMA ABI from sChain +- `MANAGER_ABI_PATH` - skale-manager ABI from Mainnet +- `IMA_NETWORK_BROWSER_DATA_PATH` - path to JSON file where network-browser results will be saved + +Optional env variables: + +- `MULTICALL` - use ethers multicall provider (default: `false`) +- `CONNECTED_ONLY` - collect info only for connected chains (default: `true`) + +- `POST_ERROR_DELAY` - delay before retry if error happened in browser loop (seconds, default: `5`) +- `NETWORK_BROWSER_DELAY` - delay between iterations of the network-browser (seconds, default: `10800`) +- `NETWORK_BROWSER_TIMEOUT` - maximum amount of time allocated to the browse function (seconds, default: `1200`) +- `NETWORK_BROWSER_LOG_LEVEL` - log level (0: silly, 1: trace, 2: debug, 3: info, 4: warn, 5: error, 6: fatal, default: `1`) +- `NETWORK_BROWSER_LOG_PRETTY` - colored logs, (boolean, default: `false`) + +## Development + +To use network-browser it's recommended to use [bun.sh](https://bun.sh/), installation script can be found on their website. + +Install dependencies: + +```bash +bun install +``` + +Run in dev mode: + +```bash +bun dev +``` + +Pre-commit hook: + +```bash +bun pre +``` + +## Build & run in production mode + +For target bun: + +```bash +bun build:bun +bun network-browser.js +``` + +For target node: + +```bash +bun build:rollup +node network-browser.js +``` diff --git a/network-browser/bun.lockb b/network-browser/bun.lockb new file mode 100755 index 00000000..b9f92824 Binary files /dev/null and b/network-browser/bun.lockb differ diff --git a/network-browser/ethers_hotfix.sh b/network-browser/ethers_hotfix.sh new file mode 100644 index 00000000..d40facdf --- /dev/null +++ b/network-browser/ethers_hotfix.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Temporary hotfix for ethers/bun.sh issue +# Fix be removed when https://github.com/oven-sh/bun/issues/5309 is fixed + +set -e + +OS=$(uname) +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +FILE="$DIR/node_modules/ethers/lib.esm/utils/geturl.js" + +if [ -f "$FILE" ]; then + if [ "$OS" = "Darwin" ]; then + echo "Running on macOS..." + sed -i '' 's/body = getBytes(gunzipSync(body));/body = getBytes(gunzipSync(Buffer.from(body)));/' "$FILE" + else + echo "Running on other OS..." + sed -i 's/body = getBytes(gunzipSync(body));/body = getBytes(gunzipSync(Buffer.from(body)));/' "$FILE" + fi + echo "The line has been replaced in $FILE." +else + echo "Error: $FILE does not exist." +fi diff --git a/network-browser/index.ts b/network-browser/index.ts new file mode 100644 index 00000000..6fac8be5 --- /dev/null +++ b/network-browser/index.ts @@ -0,0 +1,82 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +/** + * @file index.ts + * @copyright SKALE Labs 2023-Present + */ + +import { + schainsInternalContract, + nodesContract, + getMainnetManagerAbi, + getMainnetProvider +} from './src/contracts' +import { delay, pingUrl, withTimeout } from './src/tools' +import { BrowserTimeoutError } from './src/errors' +import { browse } from './src/browser' +import { + MAINNET_RPC_URL, + SCHAIN_NAME, + NETWORK_BROWSER_TIMEOUT, + POST_ERROR_DELAY, + NETWORK_BROWSER_DELAY, + MULTICALL, + CONNECTED_ONLY, + SCHAIN_RPC_URL, + LOG_LEVEL, + LOG_PRETTY +} from './src/constants' + +import { Logger, type ILogObj } from 'tslog' + +const log = new Logger({ minLevel: LOG_LEVEL, stylePrettyLogs: LOG_PRETTY }) + +async function safeNetworkBrowserLoop() { + log.info(`Running network-browser...`) + log.info(`SCHAIN_NAME: ${SCHAIN_NAME}`) + log.info(`POST_ERROR_DELAY: ${POST_ERROR_DELAY}`) + log.info(`MULTICALL: ${MULTICALL}`) + log.info(`CONNECTED_ONLY: ${CONNECTED_ONLY}`) + log.info(`NETWORK_BROWSER_TIMEOUT: ${NETWORK_BROWSER_TIMEOUT}`) + log.info(`NETWORK_BROWSER_DELAY: ${NETWORK_BROWSER_DELAY}`) + + const provider = await getMainnetProvider(MAINNET_RPC_URL, MULTICALL) + const managerAbi = getMainnetManagerAbi() + const schainsInternal = schainsInternalContract(managerAbi, provider) + const nodes = nodesContract(managerAbi, provider) + log.info(`Trying to connect to the sChain RPC: ${SCHAIN_RPC_URL}`) + await pingUrl(SCHAIN_RPC_URL) + while (true) { + try { + await withTimeout(browse(schainsInternal, nodes), NETWORK_BROWSER_TIMEOUT) + await delay(NETWORK_BROWSER_DELAY) + } catch (error) { + if (error instanceof BrowserTimeoutError) { + console.error( + `A timeout (${NETWORK_BROWSER_TIMEOUT} ms) error occurred:`, + error.message + ) + } else { + console.error('An error occurred in browse:', error) + } + await delay(POST_ERROR_DELAY) + } + } +} + +safeNetworkBrowserLoop() diff --git a/network-browser/package.json b/network-browser/package.json new file mode 100644 index 00000000..6fe9fcee --- /dev/null +++ b/network-browser/package.json @@ -0,0 +1,35 @@ +{ + "name": "network-browser", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "bun _fix && bun --hot index.ts", + "browse": "bun _fix && bun index.ts", + "build:rollup": "rollup index.ts --config rollup.config.js", + "build:bun": "bun build index.ts --target=bun --outfile=network-browser.js", + "build:node": "bun build index.ts --target=node --outfile=network-browser.js", + "build:exec": "bun build index.ts --compile --outfile=network-browser", + "lint-check": "eslint -c .eslintrc.cjs ./src/* && prettier --check \"**/*.{ts,tsx,js,mdx}\"", + "lint-fix": "bun _lint && bun _prettier", + "_prettier": "prettier --write \"**/*.{ts,tsx,js,mdx}\"", + "_lint": "eslint -c .eslintrc.cjs --fix ./src/*", + "_fix": "bash ethers_hotfix.sh" + }, + "dependencies": { + "ethers": "6.8.1", + "ethers-multicall-provider": "^5.0.0", + "tslog": "^4.9.2" + }, + "devDependencies": { + "@rollup/plugin-typescript": "^11.1.5", + "@types/elliptic": "^6.4.18", + "bun-types": "^1.0.18-1", + "elliptic": "^6.5.4", + "eslint": "^8.53.0", + "eslint-config-standard-with-typescript": "^39.1.1", + "prettier": "^3.1.0", + "rollup": "^4.7.0", + "typescript": "5.3.3" + } +} \ No newline at end of file diff --git a/network-browser/rollup.config.js b/network-browser/rollup.config.js new file mode 100644 index 00000000..626aecfc --- /dev/null +++ b/network-browser/rollup.config.js @@ -0,0 +1,16 @@ +import typescript from '@rollup/plugin-typescript' + +export default { + input: 'index.ts', + output: { + dir: 'build', + format: 'es', + preserveModules: true + }, + plugins: [ + typescript({ + include: ['src/**', 'index.ts'], + exclude: ['**/tests', '**/build'] + }) + ] +} diff --git a/network-browser/run_tests.sh b/network-browser/run_tests.sh new file mode 100644 index 00000000..03c1bd01 --- /dev/null +++ b/network-browser/run_tests.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +export IMA_NETWORK_BROWSER_DATA_PATH="$DIR/test_schainsData.json" +export SCHAIN_PROXY_PATH="$DIR/test_ima_schain.json" +export MANAGER_ABI_PATH="$DIR/../helper-scripts/contracts_data/manager.json" + +export MAINNET_RPC_URL="http://127.0.0.1:8545" +export SCHAIN_RPC_URL="http://127.0.0.1:8545" +export SCHAIN_NAME="test" +export CONNECTED_ONLY=false + +bun test \ No newline at end of file diff --git a/network-browser/src/browser.ts b/network-browser/src/browser.ts new file mode 100644 index 00000000..0a344679 --- /dev/null +++ b/network-browser/src/browser.ts @@ -0,0 +1,69 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file browser.ts + * @copyright SKALE Labs 2023-Present + */ + +import { type Contract } from 'ethers' +import { Logger, type ILogObj } from 'tslog' + +import { type SChain, type NetworkBrowserData } from './interfaces' +import { + getSChainHashes, + getSChains, + filterConnectedOnly, + getNodeIdsInGroups, + filterConnectedHashes +} from './schains' +import { getNodesGroups } from './nodes' +import { CONNECTED_ONLY, IMA_NETWORK_BROWSER_DATA_PATH, LOG_LEVEL, LOG_PRETTY } from './constants' +import { writeJson, currentTimestamp, chainIdInt } from './tools' + +const log = new Logger({ minLevel: LOG_LEVEL, stylePrettyLogs: LOG_PRETTY }) + +export async function browse(schainsInternal: Contract, nodes: Contract): Promise { + log.info('Browse iteration started, collecting chains') + + const start = performance.now() + let schainsHashes = await getSChainHashes(schainsInternal) + let schains: SChain[] = await getSChains(schainsInternal, schainsHashes) + if (CONNECTED_ONLY) { + schains = await filterConnectedOnly(schains) + schainsHashes = filterConnectedHashes(schainsHashes, schains) + } + log.info(`Going to gather information about ${schains.length} chains`) + + const nodesInGroups = await getNodeIdsInGroups(schainsInternal, schainsHashes) + const nodesInfo = await getNodesGroups(nodes, schainsInternal, nodesInGroups, schainsHashes) + schains.forEach((schain, index) => { + schain.chainId = chainIdInt(schain.name) + schain.nodes = nodesInfo[index] + }) + + const networkBrowserData: NetworkBrowserData = { + updatedAt: currentTimestamp(), + schains + } + writeJson(IMA_NETWORK_BROWSER_DATA_PATH, networkBrowserData) + const execTime = performance.now() - start + log.info( + `Browse execution time: ${execTime} ms (${execTime / 1000} s) for ${schains.length} chains` + ) +} diff --git a/network-browser/src/constants.ts b/network-browser/src/constants.ts new file mode 100644 index 00000000..f3843661 --- /dev/null +++ b/network-browser/src/constants.ts @@ -0,0 +1,58 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +/** + * @file constants.ts + * @copyright SKALE Labs 2023-Present + */ + +import { requiredEnv, booleanEnv, secondsEnv, optionalEnvNumber } from './envTools' + +// internal + +export const PORTS_PER_SCHAIN = 64 +export const DEFAULT_PING_DELAY = 10000 +export const DEFAULT_PING_ITERATIONS = 50000 + +export const NETWORKS_WITH_MULTICALL = [ + 1n, // mainnet + 3n, // ropsten + 4n, // rinkeby + 5n // goerli +] + +// required + +export const MAINNET_RPC_URL = requiredEnv('MAINNET_RPC_URL') +export const SCHAIN_RPC_URL = requiredEnv('SCHAIN_RPC_URL') +export const SCHAIN_NAME = requiredEnv('SCHAIN_NAME') + +export const SCHAIN_PROXY_PATH = requiredEnv('SCHAIN_PROXY_PATH') +export const MANAGER_ABI_PATH = requiredEnv('MANAGER_ABI_PATH') +export const IMA_NETWORK_BROWSER_DATA_PATH = requiredEnv('IMA_NETWORK_BROWSER_DATA_PATH') + +// optional + +export const MULTICALL = booleanEnv('MULTICALL', true) +export const CONNECTED_ONLY = booleanEnv('CONNECTED_ONLY', true) + +export const POST_ERROR_DELAY = secondsEnv(process.env.POST_ERROR_DELAY, 5) +export const NETWORK_BROWSER_DELAY = secondsEnv(process.env.NETWORK_BROWSER_DELAY, 10800) +export const NETWORK_BROWSER_TIMEOUT = secondsEnv(process.env.NETWORK_BROWSER_TIMEOUT, 1200) + +export const LOG_LEVEL = optionalEnvNumber('NETWORK_BROWSER_LOG_LEVEL', 1) +export const LOG_PRETTY = booleanEnv('NETWORK_BROWSER_LOG_PRETTY', false) diff --git a/network-browser/src/contracts.ts b/network-browser/src/contracts.ts new file mode 100644 index 00000000..2992c367 --- /dev/null +++ b/network-browser/src/contracts.ts @@ -0,0 +1,65 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file contracts.ts + * @copyright SKALE Labs 2023-Present + */ + +import { JsonRpcProvider, type Provider, Contract, type Network, type ContractRunner } from 'ethers' +import mc from 'ethers-multicall-provider' +import { type SkaleManagerAbi, type SChainImaAbi } from './interfaces' +import { readJson } from './tools' + +import { SCHAIN_PROXY_PATH, MANAGER_ABI_PATH, NETWORKS_WITH_MULTICALL } from './constants' + +export function getSChainImaAbi(): SChainImaAbi { + return readJson(SCHAIN_PROXY_PATH) +} + +export function getMainnetManagerAbi(): SkaleManagerAbi { + return readJson(MANAGER_ABI_PATH) +} + +function hasMulticall(network: Network): boolean { + return NETWORKS_WITH_MULTICALL.includes(network.chainId) +} + +export async function getMainnetProvider(endpoint: string, multicall: boolean): Promise { + const nativeProvider = new JsonRpcProvider(endpoint) + const network = await nativeProvider.getNetwork() + return multicall && hasMulticall(network) + ? mc.MulticallWrapper.wrap(nativeProvider) + : nativeProvider +} + +export function getSChainProvider(endpoint: string): Provider { + return new JsonRpcProvider(endpoint) +} + +export function schainsInternalContract(abi: SkaleManagerAbi, runner: ContractRunner): Contract { + return new Contract(abi.schains_internal_address, abi.schains_internal_abi, runner) +} + +export function messageProxyContract(abi: SChainImaAbi, runner: ContractRunner): Contract { + return new Contract(abi.message_proxy_chain_address, abi.message_proxy_chain_abi, runner) +} + +export function nodesContract(abi: SkaleManagerAbi, runner: ContractRunner): Contract { + return new Contract(abi.nodes_address, abi.nodes_abi, runner) +} diff --git a/network-browser/src/dataclasses.ts b/network-browser/src/dataclasses.ts new file mode 100644 index 00000000..8012c70e --- /dev/null +++ b/network-browser/src/dataclasses.ts @@ -0,0 +1,35 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file dataclasses.ts + * @copyright SKALE Labs 2023-Present + */ + +export enum SkaledPorts { + PROPOSAL = 0, + CATCHUP = 1, + WS_JSON = 2, + HTTP_JSON = 3, + BINARY_CONSENSUS = 4, + ZMQ_BROADCAST = 5, + IMA_MONITORING = 6, + WSS_JSON = 7, + HTTPS_JSON = 8, + INFO_HTTP_JSON = 9 +} diff --git a/network-browser/src/endpoints.ts b/network-browser/src/endpoints.ts new file mode 100644 index 00000000..6a69ff63 --- /dev/null +++ b/network-browser/src/endpoints.ts @@ -0,0 +1,88 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +/** + * @file endpoints.ts + * @copyright SKALE Labs 2023-Present + */ + +import { PORTS_PER_SCHAIN } from './constants' +import { + type Node, + type NodeEndpoints, + type SChainPorts, + type EndpointsSet, + type PortProtocols +} from './interfaces' +import { SkaledPorts } from './dataclasses' + +const URL_PREFIXES: EndpointsSet = { + http: 'http://', + https: 'https://', + ws: 'ws://', + wss: 'wss://', + infoHttp: 'http://' +} + +function getSChainIndexInNode(schainHash: string, schainHashes: string[]): number { + const index = schainHashes.findIndex((hash) => hash === schainHash) + if (index === -1) { + throw new Error( + `sChain ${schainHash} is not found in the list: ${JSON.stringify(schainHashes)}` + ) + } + return index +} + +function calcSChainBasePort(basePortOfNode: bigint, sChainIndex: number): bigint { + return basePortOfNode + BigInt(sChainIndex * PORTS_PER_SCHAIN) +} + +function calcPorts(schainBasePort: number): SChainPorts { + return { + http: schainBasePort + SkaledPorts.HTTP_JSON, + https: schainBasePort + SkaledPorts.HTTPS_JSON, + ws: schainBasePort + SkaledPorts.WS_JSON, + wss: schainBasePort + SkaledPorts.WSS_JSON, + infoHttp: schainBasePort + SkaledPorts.INFO_HTTP_JSON + } +} + +function composeEndpoints( + node: Node, + ports: SChainPorts, + endpointType: 'domainName' | 'publicIP' +): EndpointsSet { + const endpoints: Record = {} + for (const prefixName in URL_PREFIXES) { + const protocol = prefixName as PortProtocols + endpoints[prefixName] = `${URL_PREFIXES[protocol]}${node[endpointType]}:${ports[protocol]}` + } + return endpoints as EndpointsSet +} + +export function calcEndpoints(node: Node, schainHash: string): NodeEndpoints { + if (node.schainHashes === undefined) throw new Error('schainHashes is not found in node') + const sChainIndex = getSChainIndexInNode(schainHash, node.schainHashes) + const basePort = calcSChainBasePort(node.port, sChainIndex) + const ports = calcPorts(Number(basePort)) + return { + ports, + domain: composeEndpoints(node, ports, 'domainName'), + ip: composeEndpoints(node, ports, 'publicIP') + } +} diff --git a/network-browser/src/envTools.ts b/network-browser/src/envTools.ts new file mode 100644 index 00000000..58ff6511 --- /dev/null +++ b/network-browser/src/envTools.ts @@ -0,0 +1,63 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +/** + * @file envTools.ts + * @copyright SKALE Labs 2023-Present + */ + +const MS_MULTIPLIER = 1000 + +export function isValidNumber(str: string): boolean { + return !isNaN(+str) && str.trim().length > 0 +} + +export function secondsEnv(envValue: string | undefined, defaultSeconds: number): number { + return (envValue !== undefined ? Number(envValue) : defaultSeconds) * MS_MULTIPLIER +} + +export function booleanEnv(envVar: string, defaultValue: boolean): boolean { + const value = process.env[envVar] + if (value === undefined) { + return defaultValue + } + return value.toLowerCase() === 'true' +} + +export function requiredEnv(name: string): string { + const value = process.env[name] + if (value === undefined || value === '') { + throw new Error(`The required environment variable '${name}' is not set.`) + } + return value +} + +export function optionalEnv(name: string, defaultValue: string): string { + const value = process.env[name] + if (value === undefined) { + return defaultValue + } + return value +} + +export function optionalEnvNumber(name: string, defaultValue: number): number { + const value = process.env[name] + if (value === undefined || !isValidNumber(value)) { + return defaultValue + } + return Number(value) +} diff --git a/network-browser/src/errors.ts b/network-browser/src/errors.ts new file mode 100644 index 00000000..83833e4e --- /dev/null +++ b/network-browser/src/errors.ts @@ -0,0 +1,29 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file errors.ts + * @copyright SKALE Labs 2023-Present + */ + +export class BrowserTimeoutError extends Error { + constructor(message: string) { + super(message) + this.name = 'BrowserTimeoutError' + } +} diff --git a/network-browser/src/interfaces.ts b/network-browser/src/interfaces.ts new file mode 100644 index 00000000..d0031ff7 --- /dev/null +++ b/network-browser/src/interfaces.ts @@ -0,0 +1,106 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file interfaces.ts + * @copyright SKALE Labs 2023-Present + */ + +import { type InterfaceAbi } from 'ethers' + +export type AddressType = `0x${string}` + +export type NodeArray = [string, string, string, bigint, bigint, bigint, bigint, number, bigint] + +export interface Node { + name: string + ip: string + publicIP: string + port: bigint + startBlock: bigint + lastRewardDate: bigint + finishTime: bigint + status: number + validatorId: bigint + domainName: string + endpoints?: NodeEndpoints + schainHashes?: string[] +} + +export type PortProtocols = 'http' | 'https' | 'ws' | 'wss' | 'infoHttp' +export type SChainPorts = { [protocol in PortProtocols]: number } +export type EndpointsSet = { [protocol in PortProtocols]: string } + +export interface NodeEndpoints { + ports: { [protocol in PortProtocols]: number } + domain: EndpointsSet + ip: EndpointsSet +} + +export type SChainArray = [ + string, + string, + bigint, + bigint, + bigint, + bigint, + bigint, + bigint, + bigint, + bigint, + string +] + +export interface SChain { + name: string + mainnetOwner: string + indexInOwnerList: bigint + partOfNode: bigint + lifetime: bigint + startDate: bigint + startBlock: bigint + deposit: bigint + index: bigint + generation: bigint + originator: string + nodes?: Node[] + chainId?: number +} + +export interface SkaleManagerAbi { + nodes_address: string + nodes_abi: InterfaceAbi + schains_address: string + schains_abi: InterfaceAbi + schains_internal_address: string + schains_internal_abi: InterfaceAbi + validator_service_address: string + validator_service_abi: InterfaceAbi + skale_manager_address: string + skale_manager_abi: InterfaceAbi +} + +export interface SChainImaAbi { + message_proxy_chain_address: string + message_proxy_chain_abi: InterfaceAbi +} + +export interface NetworkBrowserData { + schains: SChain[] + updatedAt: number +} diff --git a/network-browser/src/nodes.ts b/network-browser/src/nodes.ts new file mode 100644 index 00000000..d65f547d --- /dev/null +++ b/network-browser/src/nodes.ts @@ -0,0 +1,105 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file nodes.ts + * @copyright SKALE Labs 2023-Present + */ + +import { type Contract } from 'ethers' +import { type Node, type NodeArray } from './interfaces' +import { calcEndpoints } from './endpoints' +import { hexToIp } from './tools' + +export async function getNodesGroups( + nodes: Contract, + schainsInternal: Contract, + nodeIdsArrays: bigint[][] | number[][], + schainsHashes: string[] +): Promise { + return await Promise.all( + nodeIdsArrays.map( + async (nodeIds, index) => + await getNodes(nodes, schainsInternal, nodeIds, schainsHashes[index]) + ) + ) +} + +export async function getNodes( + nodes: Contract, + schainsInternal: Contract, + nodeIds: bigint[] | number[], + schainHash: string +): Promise { + const rawNodes: Array = await getNodesRaw(nodes, schainsInternal, nodeIds) + const nodesData: Node[] = [] + for (let i = 0; i < rawNodes.length; i += 3) { + nodesData.push( + nodeStruct( + rawNodes[i] as NodeArray, + rawNodes[i + 1] as string, + schainHash, + rawNodes[i + 2] as string[], + true + ) + ) + } + return nodesData +} + +async function getNodesRaw( + nodes: Contract, + schainsInternal: Contract, + nodeIds: bigint[] | number[] +): Promise> { + return await Promise.all( + nodeIds + .map((nodeId) => [ + nodes.nodes(nodeId), + nodes.getNodeDomainName(nodeId), + schainsInternal.getSchainHashesForNode(nodeId) + ]) + .flat() + ) +} + +export function nodeStruct( + nodeArray: NodeArray, + domainName: string, + schainHash?: string, + schainHashes?: string[], + endpoints?: boolean +): Node { + const node: Node = { + name: nodeArray[0], + ip: hexToIp(nodeArray[1]), + publicIP: hexToIp(nodeArray[2]), + port: nodeArray[3], + startBlock: nodeArray[4], + lastRewardDate: nodeArray[5], + finishTime: nodeArray[6], + status: nodeArray[7], + validatorId: nodeArray[8], + domainName + } + if (schainHashes !== undefined) node.schainHashes = schainHashes + if (endpoints !== undefined && endpoints && schainHash !== undefined) { + node.endpoints = calcEndpoints(node, schainHash) + } + return node +} diff --git a/network-browser/src/schains.ts b/network-browser/src/schains.ts new file mode 100644 index 00000000..326673ee --- /dev/null +++ b/network-browser/src/schains.ts @@ -0,0 +1,98 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file schains.ts + * @copyright SKALE Labs 2023-Present + */ + +import { type Contract, id } from 'ethers' +import { type SChain, type SChainArray } from './interfaces' +import { getSChainImaAbi, getSChainProvider, messageProxyContract } from './contracts' +import { SCHAIN_RPC_URL } from './constants' + +export async function getSChainHashes(schainsInternal: Contract): Promise { + return await schainsInternal.getSchains() +} + +export async function getSChain(schainsInternal: Contract, schainName: string): Promise { + const schain: SChainArray = await schainsInternal.schains(nameToHash(schainName)) + return schainStruct(schain) +} + +export async function getAllSChains(schainsInternal: Contract): Promise { + return await getSChains(schainsInternal, await getSChainHashes(schainsInternal)) +} + +export async function getSChains( + schainsInternal: Contract, + schainsHashes: string[] +): Promise { + const schainsArray: SChainArray[] = await Promise.all( + schainsHashes.map(async (hash) => await schainsInternal.schains(hash)) + ) + return schainsArray.map((schainArray) => schainStruct(schainArray)) +} + +export async function filterConnectedOnly(schains: SChain[]): Promise { + const promiseArray = schains.map(async (schain) => { + const conditionResult = await isChainConnected(schain.name) + return { schain, conditionResult } + }) + const results = await Promise.all(promiseArray) + return results.filter((result) => result.conditionResult).map((result) => result.schain) +} + +export function filterConnectedHashes(schainsHashes: string[], schains: SChain[]): string[] { + return schainsHashes.filter((hash) => schains.some((schain) => id(schain.name) === hash)) +} + +export async function getNodeIdsInGroups( + schainsInternal: Contract, + schainsHashes: string[] +): Promise { + return await Promise.all( + schainsHashes.map(async (hash) => await schainsInternal.getNodesInGroup(hash)) + ) +} + +async function isChainConnected(schainName: string): Promise { + const provider = getSChainProvider(SCHAIN_RPC_URL) + const messageProxy = messageProxyContract(getSChainImaAbi(), provider) + return await messageProxy.isConnectedChain(schainName) +} + +function nameToHash(schainName: string): string { + return id(schainName) +} + +function schainStruct(schainArray: SChainArray): SChain { + return { + name: schainArray[0], + mainnetOwner: schainArray[1], + indexInOwnerList: schainArray[2], + partOfNode: schainArray[3], + lifetime: schainArray[4], + startDate: schainArray[5], + startBlock: schainArray[6], + deposit: schainArray[7], + index: schainArray[8], + generation: schainArray[9], + originator: schainArray[10] + } +} diff --git a/network-browser/src/tools.ts b/network-browser/src/tools.ts new file mode 100644 index 00000000..d06fcea5 --- /dev/null +++ b/network-browser/src/tools.ts @@ -0,0 +1,119 @@ +/** + * @license + * SKALE network-browser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +/** + * @file tools.ts + * @copyright SKALE Labs 2023-Present + */ + +import { id, toBeHex } from 'ethers' +import { Logger, type ILogObj } from 'tslog' + +import { readFileSync, writeFileSync, renameSync } from 'fs' +import { BrowserTimeoutError } from './errors' +import { DEFAULT_PING_DELAY, DEFAULT_PING_ITERATIONS, LOG_LEVEL, LOG_PRETTY } from './constants' + +const log = new Logger({ minLevel: LOG_LEVEL, stylePrettyLogs: LOG_PRETTY }) + +export function stringifyBigInt(obj: any): string { + return JSON.stringify( + obj, + (_, value) => (typeof value === 'bigint' ? value.toString() : value), + 4 + ) +} + +export async function delay(ms: number): Promise { + return await new Promise((resolve) => setTimeout(resolve, ms)) +} + +export async function withTimeout(promise: Promise, ms: number): Promise { + const timeout = new Promise((_resolve, reject) => { + setTimeout(() => { + reject(new BrowserTimeoutError('Operation timed out')) + }, ms) + }) + return await Promise.race([promise, timeout]) +} + +export function hexToIp(hexString: string): string { + const hex = hexString.startsWith('0x') ? hexString.slice(2) : hexString + const paddedHex = hex.padStart(8, '0') + return [ + parseInt(paddedHex.substring(0, 2), 16), + parseInt(paddedHex.substring(2, 4), 16), + parseInt(paddedHex.substring(4, 6), 16), + parseInt(paddedHex.substring(6, 8), 16) + ].join('.') +} + +export function currentTimestamp(): number { + return Math.floor(Date.now() / 1000) +} + +export function readJson(filepath: string): any { + const data = readFileSync(filepath, 'utf8') + return JSON.parse(data) +} + +export function writeJson(filepath: string, data: any): void { + log.info(`Going to save data to file: ${filepath}`) + const tmpFilepath = `${filepath}.tmp` + writeFileSync(tmpFilepath, stringifyBigInt(data), 'utf8') + moveFile(tmpFilepath, filepath) +} + +function moveFile(source: string, destination: string): void { + renameSync(source, destination) + log.info(`Successfully moved the file from ${source} to ${destination}`) +} + +export async function pingUrl( + url: string, + maxAttempts: number = DEFAULT_PING_ITERATIONS, + delay: number = DEFAULT_PING_DELAY +): Promise { + let attempt = 0 + while (attempt < maxAttempts) { + try { + const response = await fetch(url) + if (response.ok) { + log.info(`URL is available: ${url}`) + return + } else { + log.info(`Attempt ${attempt + 1} failed with status: ${response.status}`) + } + } catch (error) { + log.info( + `${url} connection failed - ${attempt + 1}/${maxAttempts}, retrying in ${ + delay / 1000 + } seconds...` + ) + } + await new Promise((resolve) => setTimeout(resolve, delay)) + attempt++ + } + log.info('Max attempts reached, URL is not available.') +} + +export function chainIdHex(schainName: string): string { + return toBeHex(id(schainName).substring(0, 15)) +} + +export function chainIdInt(schainName: string): number { + return parseInt(chainIdHex(schainName)) +} diff --git a/network-browser/tests/browser.test.ts b/network-browser/tests/browser.test.ts new file mode 100644 index 00000000..48f6afdf --- /dev/null +++ b/network-browser/tests/browser.test.ts @@ -0,0 +1,98 @@ +import { unlinkSync, existsSync } from 'node:fs' +import { describe, beforeAll, test, expect, beforeEach } from 'bun:test' +import { Contract, Wallet } from 'ethers' + +import { + getMainnetProvider, + nodesContract, + getMainnetManagerAbi, + schainsInternalContract +} from '../src/contracts' +import { browse } from '../src/browser' +import { getSChain } from '../src/schains' +import { readJson } from '../src/tools' +import { NetworkBrowserData } from '../src/interfaces' +import { MAINNET_RPC_URL, IMA_NETWORK_BROWSER_DATA_PATH } from '../src/constants' + +import { + ETH_PRIVATE_KEY, + NODES_IN_SCHAIN, + validatorsContract, + managerContract, + schainsContract, + addAllPermissions, + generateWallets, + initDefaultValidator, + linkNodes, + registerNodes, + addTestSchainTypes, + createSchain, + randomString +} from './testUtils' + +describe('browser module test', () => { + let nodes: Contract + let schainsInternal: Contract + let wallet: Wallet + const chainName = randomString() + + beforeAll(async () => { + console.log('initializing provider and contracts') + const provider = await getMainnetProvider(MAINNET_RPC_URL, false) + wallet = new Wallet(ETH_PRIVATE_KEY, provider) + + const managerAbi = getMainnetManagerAbi() + const validators = validatorsContract(managerAbi, wallet) + schainsInternal = schainsInternalContract(managerAbi, wallet) + const schains = schainsContract(managerAbi, wallet) + const manager = managerContract(managerAbi, wallet) + + nodes = nodesContract(managerAbi, provider) + + await addAllPermissions(validators, schainsInternal, schains, wallet) + await initDefaultValidator(validators) + const wallets = await generateWallets(provider, wallet, NODES_IN_SCHAIN) + await linkNodes(validators, wallet, wallets) + await registerNodes(nodes, wallets) + + await addTestSchainTypes(schainsInternal) + await createSchain(schains, chainName, wallet.address) + }) + + beforeEach(async () => { + if (existsSync(IMA_NETWORK_BROWSER_DATA_PATH)) { + console.log('removing browse results') + unlinkSync(IMA_NETWORK_BROWSER_DATA_PATH) + } + }) + + test('browse', async () => { + const schain = await getSChain(schainsInternal, chainName) + expect(schain.name).toBe(chainName) + expect(schain.mainnetOwner).toBe(wallet.address) + + expect(existsSync(IMA_NETWORK_BROWSER_DATA_PATH)).toBeFalse + await browse(schainsInternal, nodes) + expect(existsSync(IMA_NETWORK_BROWSER_DATA_PATH)).toBeTrue + + const nbData: NetworkBrowserData = readJson(IMA_NETWORK_BROWSER_DATA_PATH) + + expect(nbData.updatedAt).toBeNumber + expect(nbData.schains).toBeArray + expect(nbData.schains[0].name).toBeString + expect(nbData.schains[0].mainnetOwner).toBeString + expect(nbData.schains[0].indexInOwnerList).toBeString + expect(nbData.schains[0].partOfNode).toBeString + expect(nbData.schains[0].lifetime).toBeString + expect(nbData.schains[0].startBlock).toBeString + expect(nbData.schains[0].deposit).toBeString + expect(nbData.schains[0].index).toBeString + expect(nbData.schains[0].generation).toBeString + expect(nbData.schains[0].chainId).toBeNumber + expect(nbData.schains[0].nodes).toBeArrayOfSize(NODES_IN_SCHAIN) + if (nbData.schains[0].nodes) { + expect(nbData.schains[0].nodes[0].endpoints?.domain.https).toBeString + expect(nbData.schains[0].nodes[0].endpoints?.ip.ws).toBeString + } + }) +}) diff --git a/network-browser/tests/nodes.test.ts b/network-browser/tests/nodes.test.ts new file mode 100644 index 00000000..ec82b90b --- /dev/null +++ b/network-browser/tests/nodes.test.ts @@ -0,0 +1,82 @@ +import { describe, beforeAll, test, expect } from 'bun:test' +import { Contract, Wallet, id } from 'ethers' + +import { + getMainnetProvider, + nodesContract, + getMainnetManagerAbi, + schainsInternalContract +} from '../src/contracts' +import { getNodes } from '../src/nodes' +import { getNodeIdsInGroups } from '../src/schains' +import { MAINNET_RPC_URL } from '../src/constants' +import { Node } from '../src/interfaces' + +import { + ETH_PRIVATE_KEY, + NODES_IN_SCHAIN, + validatorsContract, + managerContract, + schainsContract, + addAllPermissions, + generateWallets, + initDefaultValidator, + linkNodes, + registerNodes, + addTestSchainTypes, + createSchain, + randomString, + nodeNamesToIds +} from './testUtils' + +describe('nodes module test', () => { + let nodes: Contract + let schainsInternal: Contract + let wallet: Wallet + let nodeNames: string[] + let nodeIds: number[] + const chainName = randomString() + + beforeAll(async () => { + console.log('initializing provider and contracts') + const provider = await getMainnetProvider(MAINNET_RPC_URL, false) + wallet = new Wallet(ETH_PRIVATE_KEY, provider) + + const managerAbi = getMainnetManagerAbi() + const validators = validatorsContract(managerAbi, wallet) + schainsInternal = schainsInternalContract(managerAbi, wallet) + const schains = schainsContract(managerAbi, wallet) + const manager = managerContract(managerAbi, wallet) + + nodes = nodesContract(managerAbi, provider) + + await addAllPermissions(validators, schainsInternal, schains, wallet) + await initDefaultValidator(validators) + const wallets = await generateWallets(provider, wallet, NODES_IN_SCHAIN) + await linkNodes(validators, wallet, wallets) + nodeNames = await registerNodes(nodes, wallets) + + nodeIds = await nodeNamesToIds(nodes, nodeNames) + + await addTestSchainTypes(schainsInternal) + await createSchain(schains, chainName, wallet.address) + }) + test('getNodes', async () => { + const chainHash = id(chainName) + const nodeIds = await getNodeIdsInGroups(schainsInternal, [chainHash]) + const nodesRes: Node[] = await getNodes(nodes, schainsInternal, nodeIds[0], chainHash) + + expect(nodesRes).toBeArrayOfSize(NODES_IN_SCHAIN) + + expect(nodesRes[0].endpoints).toBeDefined + expect(nodesRes[0].endpoints?.domain.http).toBeString + expect(nodesRes[0].endpoints?.domain.https).toBeString + expect(nodesRes[0].endpoints?.domain.ws).toBeString + expect(nodesRes[0].endpoints?.domain.wss).toBeString + + expect(nodesRes[0].endpoints?.ip.http).toBeString + expect(nodesRes[0].endpoints?.ip.https).toBeString + expect(nodesRes[0].endpoints?.ip.ws).toBeString + expect(nodesRes[0].endpoints?.ip.wss).toBeString + }) +}) diff --git a/network-browser/tests/testUtils.ts b/network-browser/tests/testUtils.ts new file mode 100644 index 00000000..f2c3cad1 --- /dev/null +++ b/network-browser/tests/testUtils.ts @@ -0,0 +1,238 @@ +import { + type Provider, + Contract, + Wallet, + TransactionReceipt, + parseEther, + Signer, + getBytes, + solidityPackedKeccak256, + BytesLike, + hexlify, + id, + zeroPadValue, + TransactionResponse +} from 'ethers' +import { ec } from 'elliptic' + +import { getMainnetManagerAbi } from '../src/contracts' + +const secp256k1EC = new ec('secp256k1') + +import { type SkaleManagerAbi } from '../src/interfaces' + +export const ETH_PRIVATE_KEY = process.env.ETH_PRIVATE_KEY! + +export const NODES_IN_SCHAIN = 16 +export const TEST_VALIDATOR_NAME = 'test_val' +const TEST_VALIDATOR_ID = 1n +const ETH_TRANSFER_AMOUNT = '0.1' +const CONFIRMATION_BLOCKS = 2 +const GAS_MULTIPLIER = 1.2 + +export function validatorsContract(abi: SkaleManagerAbi, wallet: Wallet): Contract { + return new Contract(abi.validator_service_address, abi.validator_service_abi, wallet) +} + +export function schainsContract(abi: SkaleManagerAbi, wallet: Wallet): Contract { + return new Contract(abi.schains_address, abi.schains_abi, wallet) +} + +export function managerContract(abi: SkaleManagerAbi, wallet: Wallet): Contract { + return new Contract(abi.skale_manager_address, abi.skale_manager_abi, wallet) +} + +export async function addAllPermissions( + validators: Contract, + schainsInternal: Contract, + schains: Contract, + wallet: Wallet +): Promise { + const VALIDATOR_MANAGER_ROLE = await validators.VALIDATOR_MANAGER_ROLE() + let hasRole = await validators.hasRole(VALIDATOR_MANAGER_ROLE, wallet.address) + if (!hasRole) { + console.log('granting ROLE: VALIDATOR_MANAGER_ROLE') + await sendTx(validators.grantRole, [VALIDATOR_MANAGER_ROLE, wallet.address]) + } + const SCHAIN_TYPE_MANAGER_ROLE = await schainsInternal.SCHAIN_TYPE_MANAGER_ROLE() + hasRole = await schainsInternal.hasRole(SCHAIN_TYPE_MANAGER_ROLE, wallet.address) + if (!hasRole) { + console.log('granting ROLE: SCHAIN_TYPE_MANAGER_ROLE') + await sendTx(schainsInternal.grantRole, [SCHAIN_TYPE_MANAGER_ROLE, wallet.address]) + } + const SCHAIN_CREATOR_ROLE = await schains.SCHAIN_CREATOR_ROLE() + hasRole = await schains.hasRole(SCHAIN_CREATOR_ROLE, wallet.address) + if (!hasRole) { + console.log('granting ROLE: SCHAIN_CREATOR_ROLE') + await sendTx(schains.grantRole, [SCHAIN_CREATOR_ROLE, wallet.address]) + } + console.log('all roles granted') +} + +export async function initDefaultValidator(validators: Contract): Promise { + let number = await validators.numberOfValidators() + if (number === 0n) { + console.log('going to register validator') + await sendTx(validators.registerValidator, [TEST_VALIDATOR_NAME, '', 10, 0]) + + number = await validators.numberOfValidators() + console.log(`number of active validators: ${number}`) + + console.log('going to enable validator') + await sendTx(validators.enableValidator, [1]) + console.log('validator registered and enabled') + } else { + console.log('validator already exist, skipping') + } +} + +export async function generateWallets( + provider: Provider, + adminWallet: Wallet, + num: number +): Promise { + const wallets: Promise[] = [] + const baseNonce = await adminWallet.getNonce() + for (let i = 0; i < num; i++) { + console.log(`${i + 1}/${num} generating new wallet...`) + wallets.push(generateWallet(provider, adminWallet, baseNonce + i)) + } + return await Promise.all(wallets) +} + +export async function generateWallet( + provider: Provider, + adminWallet: Wallet, + nonce?: number +): Promise { + const wallet = Wallet.createRandom() + wallet.connect(provider) + await sendEth(adminWallet, wallet.address, ETH_TRANSFER_AMOUNT, provider, nonce) + console.log(`new wallet generated: ${wallet.address}, eth transferred: ${ETH_TRANSFER_AMOUNT}`) + return new Wallet(wallet.privateKey, provider) +} + +async function sendEth( + senderWallet: Wallet, + recipientAddress: string, + amountEth: string, // Amount in ETH, e.g., "0.1" for 0.1 ETH + provider: Provider, + nonce?: number +): Promise { + senderWallet = senderWallet.connect(provider) + const amountWei = parseEther(amountEth) + const tx = { + to: recipientAddress, + value: amountWei, + nonce: nonce + } + const txResponse = await senderWallet.sendTransaction(tx) + return provider.waitForTransaction(txResponse.hash) +} + +async function getValidatorIdSignature(validatorId: bigint, signer: Signer) { + return await signer.signMessage(getBytes(solidityPackedKeccak256(['uint'], [validatorId]))) +} + +export async function linkNodes( + validators: Contract, + adminWallet: Wallet, + wallets: Wallet[] +): Promise { + const baseNonce = await adminWallet.getNonce() + const promises = wallets.map(async (wallet, i) => { + console.log(`linking node address: ${wallet.address}`) + const signature = await getValidatorIdSignature(TEST_VALIDATOR_ID, wallet) + await validators.linkNodeAddress(wallet.address, signature, { nonce: baseNonce + i }) + console.log(`linked node address: ${wallet.address}`) + }) + await Promise.all(promises) +} + +export async function registerNodes(nodes: Contract, wallets: Wallet[]): Promise { + const promises = wallets.map(async (wallet, i) => { + console.log(`${i + 1}/${wallets.length} registering node for: ${wallet.address}`) + const managerAbi = getMainnetManagerAbi() + const manager = managerContract(managerAbi, wallet) + + const { ip, port, name, domainName, publicIp } = generateNodeInfo(wallet.address, i) + + const skaleNonce = randNum(0, 10000) + const pkPartsBytes = getPublicKey(wallet) + + await sendTx(manager.createNode, [ + port, + skaleNonce, + ipToHex(ip), + ipToHex(publicIp), + pkPartsBytes, + name, + domainName + ]) + console.log(`new node created: ${ip}:${port} - ${name} - ${domainName}`) + return name + }) + return await Promise.all(promises) +} + +export async function nodeNamesToIds(nodes: Contract, nodeNames: string[]): Promise { + const promises = nodeNames.map(async (name, i) => { + return await nodes.nodesNameToIndex(id(name)) + }) + return await Promise.all(promises) +} + +export async function addTestSchainTypes(schains: Contract): Promise { + await sendTx(schains.addSchainType, [8, NODES_IN_SCHAIN]) + await sendTx(schains.addSchainType, [8, NODES_IN_SCHAIN]) +} + +export async function createSchain(schains: Contract, name: string, owner: string): Promise { + console.log(`creating new schain: ${name}, owner: ${owner}`) + await sendTx(schains.addSchainByFoundation, [1000, 1, 10, name, owner, owner, []]) + console.log(`schain created: ${name}`) +} + +function generateNodeInfo(address: string, seed: number): any { + return { + ip: getRandomIp(), + port: 10000 + seed * randNum(1, 10), + name: `node-${address}`, + domainName: `nd.${address}.com`, + publicIp: getRandomIp() + } +} + +function getRandomIp(): string { + return `${randNum(0, 255)}.${randNum(0, 255)}.${randNum(0, 255)}.${randNum(0, 255)}` +} + +function randNum(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min +} + +function getPublicKey(wallet: Wallet): [BytesLike, BytesLike] { + const publicKey = secp256k1EC.keyFromPrivate(wallet.privateKey.slice(2)).getPublic() + const pubA = zeroPadValue(hexlify(publicKey.getX().toBuffer()), 32) + const pubB = zeroPadValue(hexlify(publicKey.getY().toBuffer()), 32) + return [pubA, pubB] +} + +function ipToHex(ip: string): string { + const parts = ip.split('.') + const hexParts = parts.map((part) => parseInt(part).toString(16).padStart(2, '0')).join('') + const hexIp = `0x${hexParts}` + return hexIp +} + +export async function sendTx(func: any, args: any[]): Promise { + const estimatedGas = await func.estimateGas(...args) + const response: TransactionResponse = await func(...args, { + gasLimit: BigInt(Math.round(Number(estimatedGas) * GAS_MULTIPLIER)) + }) + return await response.wait(CONFIRMATION_BLOCKS) +} + +export function randomString(): string { + return (Math.random() + 1).toString(36).substring(7) +} diff --git a/network-browser/tests/tools.test.ts b/network-browser/tests/tools.test.ts new file mode 100644 index 00000000..207137d3 --- /dev/null +++ b/network-browser/tests/tools.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, test } from 'bun:test' + +import { + stringifyBigInt, + hexToIp, + currentTimestamp, + delay, + withTimeout, + chainIdHex, + chainIdInt +} from '../src/tools' +import { isValidNumber } from '../src/envTools' +import { BrowserTimeoutError } from '../src/errors' + +describe('tools module test', () => { + test('stringifyBigInt', () => { + const res = stringifyBigInt({ a: 1000n }) + expect(res).toBe('{\n "a": "1000"\n}') + }) + test('hexToIp', () => { + expect(hexToIp('0x5E0C3880')).toBe('94.12.56.128') + expect(hexToIp('01010101')).toBe('1.1.1.1') + expect(hexToIp('0xFFFFFFFF')).toBe('255.255.255.255') + expect(hexToIp('0xFFFFFFFFFFFFFFFF')).toBe('255.255.255.255') + }) + test('currentTimestamp', () => { + const ts = currentTimestamp() + expect(typeof ts).toBe('number') + expect(ts.toString()).toHaveLength(10) + }) + + test('delay', () => { + expect(delay(10)).resolves + }) + + test('withTimeout', async () => { + expect(withTimeout(delay(10), 100)).resolves.toBe(undefined) + expect(withTimeout(delay(1000), 100)).rejects.toThrow( + new BrowserTimeoutError('Operation timed out') + ) + }) + + test('chainId', () => { + expect(chainIdHex('elated-tan-skat')).toBe('0x79f99296') + expect(chainIdInt('elated-tan-skat')).toBe(2046399126) + }) + + test('isValidNumber', () => { + expect(isValidNumber('123')).toBeTrue + expect(isValidNumber('12.34')).toBeTrue + expect(isValidNumber('-123')).toBeTrue + expect(isValidNumber('abc')).toBeFalse + expect(isValidNumber('123abc')).toBeFalse + expect(isValidNumber('')).toBeFalse + }) +}) diff --git a/network-browser/tsconfig.json b/network-browser/tsconfig.json new file mode 100644 index 00000000..da516b48 --- /dev/null +++ b/network-browser/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + "target": "es2022" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "module": "es2022" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + "declaration": true /* Generates corresponding '.d.ts' file. */, + "outDir": "build" /* Redirect output structure to the directory. */, + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + "resolveJsonModule": true, + /* Module Resolution Options */ + "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + "types": ["bun-types"] /* Type declaration files to be included in compilation. */, + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..f08e0406 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,784 @@ +{ + "name": "skale-ima-agent", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "skale-ima-agent", + "hasInstallScript": true, + "license": "AGPL-3.0", + "dependencies": { + "remark": "^15.0.1" + }, + "devDependencies": {} + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", + "integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", + "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz", + "integrity": "sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz", + "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz", + "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/remark": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", + "dependencies": { + "@types/mdast": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unified": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz", + "integrity": "sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..e9a68daf --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "skale-ima-agent", + "license": "AGPL-3.0", + "author": "SKALE Labs and contributors", + "scripts": { + "build": "cd src && yarn build && cd ..", + "rebuild": "cd src && yarn rebuild && cd ..", + "clean-build": "cd src && yarn clean-build && cd ..", + "lint-check": "cd src && yarn lint-check && cd ..", + "lint-nb": "cd network-browser && yarn lint-check && cd ..", + "install-nb": "cd network-browser && bun install && cd ..", + "lint-fix": "cd src && yarn lint-fix && cd ..", + "postinstall": "cd src && yarn install && cd .. && cd IMA && yarn install && cd .. && yarn install-nb", + "check-outdated": "yarn outdated; cd src; yarn outdated; cd ..; cd IMA; yarn outdated; cd ..", + "upgrade-to-latest": "yarn upgrade --latest; cd src; yarn upgrade --latest; cd ..; cd IMA; yarn upgrade --latest; cd .." + }, + "dependencies": { + "remark": "^15.0.1" + } +} diff --git a/runner/run.sh b/runner/run.sh new file mode 100644 index 00000000..f6d35e2e --- /dev/null +++ b/runner/run.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +# Required IMA variables + +: "${SCHAIN_DIR?Need to set SCHAIN_DIR}" + +: "${MAINNET_PROXY_PATH?Need to set MAINNET_PROXY_PATH}" +: "${SCHAIN_PROXY_PATH?Need to set SCHAIN_PROXY_PATH}" + +: "${STATE_FILE?Need to set STATE_FILE}" + +: "${SCHAIN_NAME?Need to set SCHAIN_NAME}" + +: "${SCHAIN_RPC_URL?Need to set SCHAIN_RPC_URL}" +: "${MAINNET_RPC_URL?Need to set MAINNET_RPC_URL}" + +: "${NODE_NUMBER?Need to set NODE_NUMBER}" +: "${NODES_COUNT?Need to set NODES_COUNT}" + +: "${RPC_PORT?Need to set RPC_PORT}" +: "${MONITORING_PORT?Need to set MONITORING_PORT}" + +: "${TM_URL_MAIN_NET?Need to set TM_URL_MAIN_NET}" +: "${IMA_NETWORK_BROWSER_DATA_PATH?Need to set IMA_NETWORK_BROWSER_DATA_PATH}" + +# SGX variables + +: "${SGX_URL?Need to set SGX_URL}" +: "${ECDSA_KEY_NAME?Need to set ECDSA_KEY_NAME}" +: "${BLS_KEY_NAME?Need to set BLS_KEY_NAME}" +: "${SGX_SSL_KEY_PATH?Need to set SGX_SSL_KEY_PATH}" +: "${SGX_SSL_CERT_PATH?Need to set SGX_SSL_CERT_PATH}" +: "${NODE_ADDRESS?Need to set NODE_ADDRESS}" + +# Optional IMA variables + +export GAS_PRICE_MULTIPLIER=${GAS_PRICE_MULTIPLIER:-2} +export GAS_MULTIPLIER=${GAS_MULTIPLIER:-2} +export VERBOSE=${VERBOSE:-9} + +export M2S_TRANSFER_BLOCK_SIZE=${M2S_TRANSFER_BLOCK_SIZE:-4} +export S2M_TRANSFER_BLOCK_SIZE=${S2M_TRANSFER_BLOCK_SIZE:-4} +export S2S_TRANSFER_BLOCK_SIZE=${S2S_TRANSFER_BLOCK_SIZE:-4} +export M2S_MAX_TRANSACTIONS=${M2S_MAX_TRANSACTIONS:-0} +export S2M_MAX_TRANSACTIONS=${S2M_MAX_TRANSACTIONS:-0} +export S2S_MAX_TRANSACTIONS=${S2S_MAX_TRANSACTIONS:-0} +export M2S_AWAIT_BLOCKS=${M2S_AWAIT_BLOCKS:-0} +export S2M_AWAIT_BLOCKS=${S2M_AWAIT_BLOCKS:-0} +export S2S_AWAIT_BLOCKS=${S2S_AWAIT_BLOCKS:-0} +export M2S_AWAIT_TIME=${M2S_AWAIT_TIME:-0} +export S2M_AWAIT_TIME=${S2M_AWAIT_TIME:-0} +export S2S_AWAIT_TIME=${S2S_AWAIT_TIME:-0} + +export PERIOD=${PERIOD:-10} +export TIME_FRAMING=${TIME_FRAMING:-300} +export TIME_GAP=${TIME_GAP:-15} + +export CID_MAIN_NET=${CID_MAIN_NET:--4} +export CID_SCHAIN=${CID_SCHAIN:--4} + + +BASE_OPTIONS="--gas-price-multiplier=$GAS_PRICE_MULTIPLIER \ +--gas-multiplier=$GAS_MULTIPLIER \ +--verbose=$VERBOSE \ +--cross-ima \ +--json-rpc-port=$RPC_PORT \ +--s2s-enable \ +--abi-skale-manager=$MANAGER_ABI_PATH \ +--url-main-net=$MAINNET_RPC_URL \ +--url-s-chain=$SCHAIN_RPC_URL \ +--id-main-net=Mainnet \ +--id-s-chain=$SCHAIN_NAME \ +--cid-main-net=$CID_MAIN_NET \ +--cid-s-chain=$CID_SCHAIN \ +--abi-main-net=$MAINNET_PROXY_PATH \ +--abi-s-chain=$SCHAIN_PROXY_PATH \ +--sgx-url=$SGX_URL \ +--sgx-bls-key=$BLS_KEY_NAME \ +--sgx-ecdsa-key=$ECDSA_KEY_NAME \ +--sgx-ssl-key=$SGX_SSL_KEY_PATH \ +--sgx-ssl-cert=$SGX_SSL_CERT_PATH \ +--address-main-net=$NODE_ADDRESS \ +--address-s-chain=$NODE_ADDRESS \ +--sign-messages \ +--gathered \ +--expose \ +--no-expose-security-info \ +--skip-dry-run \ +--bls-glue=/ima/bls_binaries/bls_glue \ +--hash-g1=/ima/bls_binaries/hash_g1 \ +--bls-verify=/ima/bls_binaries/verify_bls \ +--m2s-transfer-block-size=$M2S_TRANSFER_BLOCK_SIZE \ +--s2m-transfer-block-size=$S2M_TRANSFER_BLOCK_SIZE \ +--s2s-transfer-block-size=$S2S_TRANSFER_BLOCK_SIZE \ +--m2s-max-transactions=$M2S_MAX_TRANSACTIONS \ +--s2m-max-transactions=$S2M_MAX_TRANSACTIONS \ +--s2s-max-transactions=$S2S_MAX_TRANSACTIONS \ +--m2s-await-blocks=$M2S_AWAIT_BLOCKS \ +--s2m-await-blocks=$S2M_AWAIT_BLOCKS \ +--s2s-await-blocks=$S2S_AWAIT_BLOCKS \ +--m2s-await-time=$M2S_AWAIT_TIME \ +--s2m-await-time=$S2M_AWAIT_TIME \ +--s2s-await-time=$S2S_AWAIT_TIME \ +--period=$PERIOD \ +--node-number=$NODE_NUMBER \ +--nodes-count=$NODES_COUNT \ +--time-framing=$TIME_FRAMING \ +--tm-url-main-net=$TM_URL_MAIN_NET \ +--time-gap=$TIME_GAP \ +--monitoring-port=$MONITORING_PORT \ +--network-browser-path=$IMA_NETWORK_BROWSER_DATA_PATH \ +--pwa \ +--no-expose-pwa \ +--auto-exit=86400" + +IMA_LOOP_CMD="node $DIR/../src/build/main.js --loop $BASE_OPTIONS" +NETWORK_BROWSER_CMD="node $DIR/../network-browser/build/index.js" + +echo "$(date) - Running IMA loop and network-browser" +node "$DIR/startup.js" "$IMA_LOOP_CMD" "$NETWORK_BROWSER_CMD" diff --git a/runner/startup.js b/runner/startup.js new file mode 100644 index 00000000..2aa84427 --- /dev/null +++ b/runner/startup.js @@ -0,0 +1,40 @@ +const { spawn } = require( "child_process" ); + +function startProcess( command ) { + // Start the process in a new process group + const parts = command.split( " " ); + return spawn( parts[0], parts.slice( 1 ), { stdio: "inherit", shell: true, detached: true } ); +} + +if( process.argv.length !== 4 ) { + console.error( "Usage: node startup.js \"\" \"\"" ); + process.exit( 1 ); +} + +const child1 = startProcess( process.argv[2] ); +const child2 = startProcess( process.argv[3] ); + +function terminateProcessGroup( processToKill ) { + if( processToKill && !processToKill.killed ) { + // Use negative PID to kill the process group + process.kill( -processToKill.pid, "SIGTERM" ); + } +} + +function onChildExit( otherChild, code, signal ) { + terminateProcessGroup( otherChild ); + // Exit with the code or signal of the process that ended first + process.exit( code || signal ); +} + +child1.on( "exit", ( code, signal ) => onChildExit( child2, code, signal ) ); +child2.on( "exit", ( code, signal ) => onChildExit( child1, code, signal ) ); + +process.on( "SIGINT", () => { + console.log( "Received SIGINT. Exiting..." ); + + terminateProcessGroup( child1 ); + terminateProcessGroup( child2 ); + + process.exit( 130 ); +} ); diff --git a/scripts/bls_binaries/.keep b/scripts/bls_binaries/.keep new file mode 100644 index 00000000..e69de29b diff --git a/scripts/build_image.sh b/scripts/build_image.sh new file mode 100755 index 00000000..7b10d9f4 --- /dev/null +++ b/scripts/build_image.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +if [ -z "$VERSION" ] +then + echo "No VERSION provided, exiting" + exit 1 +fi +DOCKER_USERNAME=$1 +DOCKER_PASSWORD=$2 + +NAME=ima +REPO_NAME=skalenetwork/$NAME +IMAGE_NAME=$REPO_NAME:$VERSION +LATEST_IMAGE_NAME=$REPO_NAME:latest + +if [ -z "$SKIP_BUILD" ] +then + + docker build -t "$IMAGE_NAME" . || exit $? + + if [ "$RELEASE" = true ] + then + docker tag "$IMAGE_NAME" "$LATEST_IMAGE_NAME" + fi +fi + +if [[ ! -z "$DOCKER_USERNAME" ]] +then + echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin + docker push "$IMAGE_NAME" || exit $? + if [ "$RELEASE" = true ] + then + docker push $LATEST_IMAGE_NAME || exit $? + fi +fi diff --git a/scripts/calculate_version.sh b/scripts/calculate_version.sh new file mode 100755 index 00000000..96860215 --- /dev/null +++ b/scripts/calculate_version.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +VERSION=$(cat VERSION) +USAGE_MSG='Usage: BRANCH=[BRANCH] calculate_version.sh' + +if [ -z "$BRANCH" ] +then + (>&2 echo 'You should provide branch') + echo "$USAGE_MSG" + exit 1 +fi + + +if [ -z "$VERSION" ]; then + echo "The base version is not set." + exit 1 +fi + +if [[ $BRANCH == 'stable' ]]; then + echo "$VERSION" + exit 1 +fi + +git fetch --tags > /dev/null + +TAG=$BRANCH +if [[ $BRANCH == v*.*.* ]]; then TAG='develop'; fi + +for (( NUMBER=0; ; NUMBER++ )) +do + FULL_VERSION="$VERSION-$TAG.$NUMBER" + if ! [[ $(git tag -l | grep "$FULL_VERSION") ]]; then + echo "$FULL_VERSION" | tr / - + break + fi +done diff --git a/scripts/config_from_accounts.py b/scripts/config_from_accounts.py new file mode 100644 index 00000000..ac19dc5f --- /dev/null +++ b/scripts/config_from_accounts.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 + +# SPDX-License-Identifier: AGPL-3.0-only + +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from sys import argv +import json + +def main(): + if len(argv) < 2: + print('Usage: config_from_accounts.py {file with accounts} {target config file}') + exit(1) + + accounts_filename, config_filename = argv[1], argv[2] + with open(accounts_filename) as accounts_file: + accounts = json.load(accounts_file) + addresses = list(accounts['private_keys'].keys()) + config = {'PRIVATE_KEY_FOR_ETHEREUM': accounts['private_keys'][addresses[0]], + 'PRIVATE_KEY_FOR_SCHAIN': accounts['private_keys'][addresses[1]]} + with open(config_filename, 'w') as config_file: + json.dump(config, config_file) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/scripts/download_binaries.sh b/scripts/download_binaries.sh new file mode 100644 index 00000000..cb588ead --- /dev/null +++ b/scripts/download_binaries.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +: "${LIB_BLS_RELEASE_TAG?Need to set LIB_BLS_RELEASE_TAG}" + +BASE_URL="https://github.com/skalenetwork/libBLS/releases/download" + +BLS_GLUE_URL=$BASE_URL/$LIB_BLS_RELEASE_TAG/bls_glue +HASH_G1_URL=$BASE_URL/$LIB_BLS_RELEASE_TAG/hash_g1 +BLS_VERIFY_URL=$BASE_URL/$LIB_BLS_RELEASE_TAG/verify_bls + +echo "Downloading BLS binaries..." +curl -L "$BLS_GLUE_URL" > "$DIR/bls_binaries/bls_glue" +curl -L "$HASH_G1_URL" > "$DIR/bls_binaries/hash_g1" +curl -L "$BLS_VERIFY_URL" > "$DIR/bls_binaries/verify_bls" diff --git a/src/.eslintrc.cjs b/src/.eslintrc.cjs new file mode 100644 index 00000000..2b01af34 --- /dev/null +++ b/src/.eslintrc.cjs @@ -0,0 +1,45 @@ +module.exports = { + "env": { + browser: false, + es2020: true, + node: true + }, + extends: "standard-with-typescript", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module" + }, + "rules": { + "linebreak-style": [ "error", "unix" ], + "eqeqeq": "off", + "comma-dangle": [ "error", "never" ], + "comma-style": [ "error", "last" ], + "space-in-parens": [ "error", "always" ], + "space-before-blocks": [ "error", "always" ], + "array-bracket-spacing": [ "error", "always" ], + "object-curly-spacing": [ "error", "always" ], + "curly": [ "error", "multi-or-nest" ], + "nonblock-statement-body-position": [ "error", "below" ], + "no-cond-assign": [ "error", "always" ], + "max-len": [ "error", { "code": 100, "tabWidth": 4 } ], + "max-lines-per-function": [ "error", { "max": 200, "skipBlankLines": false } ], + "@typescript-eslint/indent": [ "error", 4, { "ignoredNodes": [ "SwitchCase" ] } ], + "@typescript-eslint/quotes": [ "error", "double" ], + "@typescript-eslint/semi": [ "error", "always" ], + "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/keyword-spacing": [ "error", { + "overrides": { + "if": { "before": false, "after": false }, + "else": { "before": true, "after": true }, + "for": { "before": false, "after": false }, + "while": { "before": false, "after": false } + } + } ], + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/no-this-alias": "off" + } +}; diff --git a/src/CLI.md b/src/CLI.md new file mode 100644 index 00000000..c0b6e592 --- /dev/null +++ b/src/CLI.md @@ -0,0 +1,260 @@ + + +# IMA Agent Command Line Interface + +**IMA** runs message transfer loop between **Ethereum(Main Net)** and **S-Chain**, also between assigned **S-Chain** and all connected to it other **S-Chains**. IMA also offers set of test tasks in its CLI. + +IMA operates with 3 types of chains: + + - **Ethereum(Main Net)** + - **S-Chain**, source **S-Chain** + - **T-Chain**, target **S-Chain** + +In the most of use cases only **Ethereum(Main Net)** and source **S-Chain** are needed. + +**IMA** supports the following groups of operations: + + - Run message loop. + - Perform **S-Chain** registration and initialization. + - Configure and change gas reimbursement. + - Do **ETH**, **ERC20**, **ERC721**, **ERC1155**, batch **ERC1155** payments between chains. + - View amount of **ETH** can be received on Main Net. + - Mint **ERC20**, **ERC721**, **ERC1155** tokens. + - Show **ETH**, **ERC20**, **ERC721**, **ERC1155** balances. + - Browse **SKALE network**. + - Download source **S-Chain** information. + - Discover set of **S-Chains** connected to specified **S-Chains**. + - Discover chain ID of specified chain with `--discover-cid`. + - Run monitoring service and expose its JSON RPC, the `--monitoring-port=number` option turns on monitoring web socket RPC server on specified port. By default monitoring server is disabled. + +Here is list of options running operations described above: + + --show-config - Show configuration values and exit. + --show-balance - Show ETH and/or token balances on Main-net and/or S-Chain and exit. + --m2s-payment - Do one payment from Main-net user account to S-chain user account. + --s2m-payment - Do one payment from S-chain user account to Main-net user account. + --s2m-receive - Receive one payment from S-chain user account to Main-net user account(ETH only, receives all the ETH pending in transfer). + --s2m-view - View money amount user can receive as payment from S-chain user account to Main-net user account(ETH only, receives all the ETH pending in transfer). + --s2s-payment - Do one payment from S-chain user account to other S-chain user account. + --s2s-forward - Indicates S<->S transfer direction is forward. I.e. source S-chain is token minter and instantiator. This is default mode. + --s2s-reverse - Indicates S<->S transfer direction is reverse. I.e. destination S-chain is token minter and instantiator. + --m2s-transfer - Do single money transfer loop from Main-net to S-chain. + --s2m-transfer - Do single money transfer loop from S-chain to Main-net. + --s2s-transfer - Do single money transfer loop from S-chain to S-chain. + --with-metadata - Makes ERC721 transfer using special version of Token Manager to transfer token metadata. + --transfer - Run single M<->S and, optionally, S->S transfer loop iteration. + --loop - Run M<->S and, optionally, S->S transfer loop. + --browse-s-chain - Download own S-Chain network information. + --browse-skale-network - Download entire SKALE network description. + --browse-connected-schains - Download S-Chains connected to S-Chain with name specified in id-s-chain command line parameter. + --mint-erc20 - Mint ERC20 tokens. + --mint-erc721 - Mint ERC721 tokens. + --mint-erc1155 - Mint ERC1155 tokens. + --burn-erc20 - Burn ERC20 tokens. + --burn-erc721 - Burn ERC721 tokens. + --burn-erc1155 - Burn ERC1155 tokens. + +Please notice, token testing commands require `--tm-url-t-chain`, `cid-t-chain`, `erc20-t-chain` or `erc721-t-chain` or `erc1155-t-chain`, account information (like private key `key-t-chain`) command line arguments specified. Token amounts are specified via amount command line arguments specified. Token IDs are specified via tid or tids command line arguments. + +One or more of the following URL, chain name and chain ID parameters are needed for most of **IMA** operations: + + --url-main-net=URL..............Main-net URL. Value is automatically loaded from the URL_W3_ETHEREUM environment variable if not specified. + --url-s-chain=URL...............S-chain URL. Value is automatically loaded from the URL_W3_S_CHAIN environment variable if not specified. + --url-t-chain=URL...............S<->S Target S-chain URL. Value is automatically loaded from the URL_W3_S_CHAIN_TARGET environment variable if + --id-main-net=number............Main-net Ethereum network name.. Value is automatically loaded from the CHAIN_NAME_ETHEREUM environment variable if not specified. Default value is "Mainnet". + --id-s-chain=number.............S-chain Ethereum network name.. Value is automatically loaded from the CHAIN_NAME_SCHAIN environment variable if not specified. Default value is "id-S-chain". + --id-t-chain=number.............S<->S Target S-chain Ethereum network name.. Value is automatically loaded from the CHAIN_NAME_SCHAIN_TARGET environment variable if not specified. Default value is "id-T-chain". + --cid-main-net=number...........Main-net Ethereum chain ID. Value is automatically loaded from the CID_ETHEREUM environment variable if not specified. Default value is -4. + --cid-s-chain=number............S-chain Ethereum chain ID. Value is automatically loaded from the CID_SCHAIN environment variable if not specified. Default value is -4. + --cid-t-chain=number............S<->S Target S-chain Ethereum chain ID. Value is automatically loaded from the CID_SCHAIN_TARGET environment variable if not specified. Default value is -4. + +For most of operations, **IMA** needs ABIs of **Skale Manager**, **Ethereum(Main Net)**, **S-Chain(s)**: + + --abi-skale-manager=path........Path to JSON file containing Skale Manager ABI. Optional parameter. It's needed for S-Chain to S-Chain transfers. + --abi-main-net=path.............Path to JSON file containing IMA ABI for Main-net. + --abi-s-chain=path..............Path to JSON file containing IMA ABI for S-chain. + --abi-t-chain=path..............Path to JSON file containing IMA ABI for S<->S Target S-chain. + +Token transfer commands require token APIs on appropriate chains. + +**ERC20** options: + + --erc20-main-net=path...........Path to JSON file containing ERC20 ABI for Main-net. + --erc20-s-chain=path............Path to JSON file containing ERC20 ABI for S-chain. + --addr-erc20-s-chain=address....Explicit ERC20 address in S-chain. + --erc20-t-chain=path............Path to JSON file containing ERC20 ABI for S<->S Target S-chain. + --addr-erc20-t-chain=address....Explicit ERC20 address in S<->S Target S-chain. + +**ERC721** options: + + --erc721-main-net=path..........Path to JSON file containing ERC721 ABI for Main-net. + --erc721-s-chain=path...........Path to JSON file containing ERC721 ABI for S-chain. + --addr-erc721-s-chain=address...Explicit ERC721 address in S-chain. + --erc721-t-chain=path...........Path to JSON file containing ERC721 ABI for S<->S S-chain. + --addr-erc721-t-chain=address...Explicit ERC721 address in S<->S S-chain. + +**ERC1155** options: + + --erc1155-main-net=path.........Path to JSON file containing ERC1155 ABI for Main-net. + --erc1155-s-chain=path..........Path to JSON file containing ERC1155 ABI for S-chain. + --addr-erc1155-s-chain=address..Explicit ERC1155 address in S-chain. + --erc1155-t-chain=path..........Path to JSON file containing ERC1155 ABI for S<->S S-chain. + --addr-erc1155-t-chain=address..Explicit ERC1155 address in S<->S S-chain. + +**IMA** can sign transactions using one of following ways: + + - Using **Transaction Manager** JSON RPC + - Using **SGX wallet** JSON RPC + - Using explicitly specified private key + - Using wallet address, for read only operations only + +The following parameters needed to use **Transaction Manager**: + + --tm-url-main-net=URL...........Transaction Manager server URL for Main-net. Value is automatically loaded from the TRANSACTION_MANAGER_URL_ETHEREUM environment variable if not specified. Example: redis://@127.0.0.1:6379 + --tm-url-s-chain=URL............Transaction Manager server URL for S-chain. Value is automatically loaded from the TRANSACTION_MANAGER_URL_S_CHAIN environment variable if not specified. + --tm-url-t-chain=URL............Transaction Manager server URL for S<->S Target S-chain. Value is automatically loaded from the TRANSACTION_MANAGER_URL_S_CHAIN_TARGET environment variable if not specified. + --tm-priority-main-net=URL......Transaction Manager priority for Main-net. Value is automatically loaded from the TRANSACTION_MANAGER_PRIORITY_ETHEREUM environment variable if not specified. Default is 5. + --tm-priority-s-chain=URL.......Transaction Manager priority for S-chain. Value is automatically loaded from the TRANSACTION_MANAGER_PRIORITY_S_CHAIN environment variable if not specified. Default is 5. + --tm-priority-t-chain=URL.......Transaction Manager priority for S<->S Target S-chain. Value is automatically loaded from the TRANSACTION_MANAGER_PRIORITY_S_CHAIN_TARGET environment variable if not specified. Default is 5. + +The following parameters needed to use **SGX wallet**: + + --sgx-url-main-net=URL..........SGX server URL for Main-net. Value is automatically loaded from the SGX_URL_ETHEREUM environment variable if not specified. + --sgx-url-s-chain=URL...........SGX server URL for S-chain. Value is automatically loaded from the SGX_URL_S_CHAIN environment variable if not specified. + --sgx-url-t-chain=URL...........SGX server URL for S<->S Target S-chain. Value is automatically loaded from the SGX_URL_S_CHAIN_TARGET environment variable if not specified. + --sgx-ecdsa-key-main-net=name...SGX/ECDSA key name for Main-net. Value is automatically loaded from the SGX_KEY_ETHEREUM environment variable if not specified. + --sgx-ecdsa-key-s-chain=name....SGX/ECDSA key name for S-chain. Value is automatically loaded from the SGX_KEY_S_CHAIN environment variable if not specified. + --sgx-ecdsa-key-t-chain=name....SGX/ECDSA key name for S<->S Target S-chain. Value is automatically loaded from the SGX_KEY_S_CHAIN_TARGET environment variable if not specified. + --sgx-ssl-key-main-net=path.....Path to SSL key file for SGX wallet of Main-net. Value is automatically loaded from the SGX_SSL_KEY_FILE_ETHEREUM environment variable if not specified. + --sgx-ssl-key-s-chain=path......Path to SSL key file for SGX wallet of S-chain. Value is automatically loaded from the SGX_SSL_KEY_FILE_S_CHAIN environment variable if not specified. + --sgx-ssl-key-t-chain=path......Path to SSL key file for SGX wallet of S<->S Target S-chain. Value is automatically loaded from the SGX_SSL_KEY_FILE_S_CHAIN_TARGET environment variable if not specified. + --sgx-ssl-cert-main-net=path....Path to SSL certificate file for SGX wallet of Main-net. Value is automatically loaded from the SGX_SSL_CERT_FILE_ETHEREUM environment variable if not specified. + --sgx-ssl-cert-s-chain=path.....Path to SSL certificate file for SGX wallet of S-chain. Value is automatically loaded from the SGX_SSL_CERT_FILE_S_CHAIN environment variable if not specified. + --sgx-ssl-cert-t-chain=path.....Path to SSL certificate file for SGX wallet of S<->S Target S-chain. Value is automatically loaded from the SGX_SSL_CERT_FILE_S_CHAIN_TARGET environment variable if not specified. + +Using explicitly specified private key: + + --address-main-net=value........Main-net user account address. Value is automatically loaded from the ACCOUNT_FOR_ETHEREUM environment variable if not specified. + --address-s-chain=value.........S-chain user account address. Value is automatically loaded from the ACCOUNT_FOR_SCHAIN environment variable if not specified. + --address-t-chain=value.........S<->S Target S-chain user account address. Value is automatically loaded from the ACCOUNT_FOR_SCHAIN_TARGET environment variable if not specified. + +For read only operations, only wallet address can be specified: + + --key-main-net=value............Private key for Main-net user account address. Value is automatically loaded from the PRIVATE_KEY_FOR_ETHEREUM environment variable if not specified. + --key-s-chain=value.............Private key for S-Chain user account address. Value is automatically loaded from the PRIVATE_KEY_FOR_SCHAIN environment variable if not specified. + --key-t-chain=value.............Private key for S<->S Target S-Chain user account address. Value is automatically loaded from the PRIVATE_KEY_FOR_SCHAIN_TARGET environment variable if not specified. + +Please notice, **IMA** prefer to use transaction manager to sign blockchain transactions if `--tm-url-main-net`/`--tm-url-s-chain` command line values or `TRANSACTION_MANAGER_URL_ETHEREUM`/`TRANSACTION_MANAGER_URL_S_CHAIN` shell variables were specified. Next preferred option is **SGX wallet** which is used if `--sgx-url-main-net`/`--sgx-url-s-chain` command line values or `SGX_URL_ETHEREUM`/`SGX_URL_S_CHAIN` shell variables were specified. SGX signing also needs key name, key and certificate files. Finally, **IMA** attempts to use explicitly provided private key to sign blockchain transactions if `--key-main-net`/`--key-s-chain` command line values or `PRIVATE_KEY_FOR_ETHEREUM`/`PRIVATE_KEY_FOR_SCHAIN` shell variables were specified. + +**ETH** transfers operations require amount of **ETH** to be specified with one of the following options: + + --value=numberUnitName..........Amount of unitName to transfer, where unitName is well known Ethereum unit name like ether or wei. + --wei=number....................Amount of wei to transfer. + --babbage=number................Amount of babbage(wei*1000) to transfer. + --lovelace=number...............Amount of lovelace(wei*1000*1000) to transfer. + --shannon=number................Amount of shannon(wei*1000*1000*1000) to transfer. + --szabo=number..................Amount of szabo(wei*1000*1000*1000*1000) to transfer. + --finney=number.................Amount of finney(wei*1000*1000*1000*1000*1000) to transfer. + --ether=number..................Amount of ether(wei*1000*1000*1000*1000*1000*1000) to transfer. + +Token transfer operations require token amounts and/or token IDs: + + --amount=number.................Amount of tokens to transfer. + --tid=number....................ERC721 or ERC1155 token id to transfer. + --amounts=array of numbers......ERC1155 token id to transfer in batch. + --tids=array of numbers.........ERC1155 token amount to transfer in batch. + --sleep-between-tx=number.......Sleep time (in milliseconds) between transactions during complex operations. + --wait-next-block...............Wait for next block between transactions during complex operations. + +**Gas reimbursement** can be configure with the following options: + + --reimbursement-chain=name......Specifies chain name. + --reimbursement-recharge=vu.....Recharge user wallet with specified value v, unit name u is well known Ethereum unit name like ether or wei. + --reimbursement-withdraw=vu.....Withdraw user wallet with specified value v, unit name u is well known Ethereum unit name like ether or wei. + --reimbursement-balance.........Show wallet balance. + --reimbursement-range=number....Sets minimal time interval between transfers from S-Chain to Main Net. + +**Gas reimbursement** can be **Oracle**-based if the following options are specified: + + --enable-oracle.................Enable call to Oracle to compute gas price for gas reimbursement. Default mode. + --disable-oracle................Disable call to Oracle to compute gas price for gas reimbursement. + +**IMA** must be initialized and its **S-Chain** must be registered once after creation with the following options: + + --register......................Register(perform all steps). + --check-registration............Perform registration status check(perform all steps). + +**S-Chain** to **S-Chain** transfers must be turned on and require periodic **SKALE network re-discovery**: + + --s2s-enable....................Enables S-Chain to S-Chain transfers. Default mode. The abi-skale-manager path must be provided. + --s2s-disable...................Disables S-Chain to S-Chain transfers. + --net-rediscover=number.........SKALE NETWORK re-discovery interval(in seconds). Default is 3600 seconds or 1 hour, specify 0 to disable SKALE NETWORK re-discovery. + +**IMA** loop can optionally use dry run, group **IMA** messages and supports various customizations: + + --no-wait-s-chain...............Do not wait until S-Chain is started. + --max-wait-attempts=value.......Max number of S-Chain call attempts to do while it became alive and sane. + --skip-dry-run..................Skip dry run contract method calls. + --dry-run.......................Use error results of dry run contract method calls as actual errors and stop execute. + --m2s-transfer-block-size.......Number of transactions in one block to use in money transfer loop from Main-net to S-chain. + --s2m-transfer-block-size.......Number of transactions in one block to use in money transfer loop from S-chain to Main-net. + --s2s-transfer-block-size.......Number of transactions in one block to use in money transfer loop from S-chain to S-chain. + --transfer-block-size...........Number of transactions in one block to use in both money transfer loops. + --m2s-max-transactions..........Maximal number of transactions to do in money transfer loop from Main-net to S-chain(0 is unlimited). + --s2m-max-transactions..........Maximal number of transactions to do in money transfer loop from S-chain to Main-net(0 is unlimited). + --s2s-max-transactions..........Maximal number of transactions to do in money transfer loop from S-chain to S-chain(0 is unlimited). + --max-transactions..............Maximal number of transactions to do in both money transfer loops(0 is unlimited). + --m2s-await-blocks..............Maximal number of blocks to wait to appear in blockchain before transaction from Main-net to S-chain(0 is no wait). + --s2m-await-blocks..............Maximal number of blocks to wait to appear in blockchain before transaction from S-chain to Main-net(0 is no wait). + --s2s-await-blocks..............Maximal number of blocks to wait to appear in blockchain before transaction from S-chain to S-chain(0 is no wait). + --await-blocks..................Maximal number of blocks to wait to appear in blockchain before transaction between both S-chain and Main-net(0 is no wait). + --m2s-await-time................Minimal age of transaction message(in seconds) before it will be transferred from Main-net to S-chain(0 is no wait). + --s2m-await-time................Minimal age of transaction message(in seconds) before it will be transferred from S-chain to Main-net(0 is no wait). + --s2s-await-time................Minimal age of transaction message(in seconds) before it will be transferred from S-chain to S-chain(0 is no wait). + --await-time....................Minimal age of transaction message(in seconds) before it will be transferred between both S-chain and Main-net(0 is no wait). + --period........................Transfer loop period(in seconds). + --node-number=value.............S-Chain node number(0-based). + --nodes-count=value.............S-Chain nodes count. + --time-framing=value............Specifies period(in seconds) for time framing(0 to disable time framing). + --time-gap=value................Specifies gap(in seconds) before next time frame. + +**IMA** transfer loop must **BLS**-sign messages and needs paths to **BLS** command line utilities: + + --sign-messages.................Sign transferred messages. + --bls-glue=path.................Specifies path to bls_glue application. + --hash-g1=path..................Specifies path to hash_g1 application. + --bls-verify=path...............Optional parameter, specifies path to verify_bls application. + +**IMA** transfer loop needs to scan **IMA smart contract** events, scanning can be customized with the following options: + + --bs-step-size=number...........Specifies step block range size to search iterative past events step by step. 0 to disable iterative search. + --bs-max-all-range=number.......Specifies max number of steps to allow to search as [0...latest] range. 0 to disable iterative search. + --bs-progressive-enable.........Enables progressive block scan to search past events. + --bs-progressive-disable........Disables progressive block scan to search past events. + +**IMA** pending work analysis subsystem allows to detect busy state of previous **IMA Agent** running long work outside its time frame: + + --pwa...........................Enable pending work analysis to avoid transaction conflicts. Default mode. + --no-pwa........................Disable pending work analysis. Not recommended for slow and overloaded blockchains. + --pwa-timeout=seconds...........Node state timeout during pending work analysis. Default is 60 seconds. + +Like any command line application, **IMA** produces various command line output and supports logging. Logging can be customized with the following options: + + --expose........................Expose low-level log details after successful operations. By default details exposed only on errors. + --no-expose.....................Expose low-level log details only after errors. Default expose mode. + --verbose=value.................Set level of output details. + --verbose-list..................List available verbose levels and exit. + --log=path......................Write program output to specified log file(multiple files can be specified). + --log-size=value................Max size(in bytes) of one log file(affects to log log rotation). + --log-files=value...............Maximum number of log files for log rotation. + --gathered......................Print details of gathering data from command line arguments. Default mode. + --no-gathered...................Do not print details of gathering data from command line arguments. + --expose-security-info..........Expose security-related values in log output. This mode is needed for debugging purposes only. + --no-expose-security-info.......Do not expose security-related values in log output. Default mode. + --expose-pwa....................Expose IMA agent pending work analysis information + --no-expose-pwa.................Do not expose IMA agent pending work analysis information. Default mode. + +Command line output and logging can be plain or ANSI-colorized: + + --colors........................Use ANSI-colorized logging. + --no-colors.....................Use monochrome logging. diff --git a/src/IMA-CORE.md b/src/IMA-CORE.md new file mode 100644 index 00000000..579209fc --- /dev/null +++ b/src/IMA-CORE.md @@ -0,0 +1,119 @@ + + +# IMA Agent Core module + +Implements **SKALE Interchain Messaging Agent** APIs. + +Typical usage: + + const IMA = require( "../npms/skale-ima" ); + const cc = IMA.cc; + const log = IMA.log; + const w3mod = IMA.w3mod; + let ethereumjs_tx = IMA.ethereumjs_tx; + let ethereumjs_wallet = IMA.ethereumjs_wallet; + let ethereumjs_util = IMA.ethereumjs_util; + +All the **IMA** require externally pre-initialized **Web3** connections, ABI, contract and account description objects. + +Mainnet and S-Chains should be pre initialized as following: + + let g_str_url_main_net = "http://127.0.0.1:8545"; + let g_str_url_s_chain = "http://127.0.0.1:2231"; + let g_chain_id_main_net = "Mainnet"; + let g_chain_id_s_chain = "id-S-chain"; + const g_w3_main_net = new w3mod( new w3mod.providers.HttpProvider( g_str_url_main_net ) ); + const g_w3_s_chain = new w3mod( new w3mod.providers.HttpProvider( g_str_url_s_chain ) ); + +The **joTrufflePublishResult_main_net** and **joTrufflePublishResult_s_chain** ABI description objects are ABIs loaded from **truffle**-generated files. They are used to initialize contract description objects: + + let g_jo_deposit_box = new g_w3_main_net.eth.Contract( joTrufflePublishResult_main_net.deposit_box_abi, joTrufflePublishResult_main_net.deposit_box_address ); // only main net + let g_jo_token_manager = new g_w3_s_chain .eth.Contract( joTrufflePublishResult_s_chain .token_manager_abi, joTrufflePublishResult_s_chain .token_manager_address ); // only s-chain + let g_jo_message_proxy_main_net = new g_w3_main_net.eth.Contract( joTrufflePublishResult_main_net.message_proxy_mainnet_abi, joTrufflePublishResult_main_net.message_proxy_mainnet_address ); + let g_jo_message_proxy_s_chain = new g_w3_s_chain .eth.Contract( joTrufflePublishResult_s_chain .message_proxy_chain_abi, joTrufflePublishResult_s_chain .message_proxy_chain_address ); + +The following function registers new **S-Chain** on _Mainnet_ and vice versa: + + async function register_all() { + var b1 = await IMA.register_s_chain_in_deposit_box( + g_w3_main_net, + g_jo_deposit_box, // only main net + joAccountMN, + g_jo_token_manager, // only s-chain + g_chain_id_s_chain, + tc_main_net + ); + var b2 = await IMA.register_main_net_depositBox_on_s_chain( + g_w3_s_chain, + g_jo_token_manager, // only s-chain + g_jo_deposit_box, // only main net + joAccountSC, + chainID_s_chain, + tc_s_chain + ); + var b3 = b1 && b2; + return b2; + } + +The following code demonstrates money transfer event processing: + + var b1 = await IMA.do_transfer( // main-net --> s-chain + /**/ g_w3_main_net, + g_jo_message_proxy_main_net, + joAccountMN, + g_w3_s_chain, + g_jo_message_proxy_s_chain, + joAccountSC, + g_chain_id_main_net, + g_chain_id_s_chain, + chainID_main_net, + g_cit_s_chain, + null, // imaState.jo_deposit_box - for logs validation on mainnet + jo_token_manager, // for logs validation on s-chain + g_nTransferBlockSizeM2S, + g_nMaxTransactionsM2S, + g_nBlockAwaitDepthM2S, + g_nBlockAgeM2S, + fn_do_sign_messages_m2s, // fn_sign_messages or null + tc_s_chain, + null // optsPendingTxAnalysis + ); + var b2 = await IMA.do_transfer( // s-chain --> main-net + /**/ g_w3_s_chain, + g_jo_message_proxy_s_chain, + joAccountSC, + g_w3_main_net, + g_jo_message_proxy_main_net, + /**/ joAccountMN, + g_chain_id_s_chain, + g_chain_id_main_net, + chainID_s_chain, + chainID_main_net, + imaState.jo_deposit_box, // for logs validation on mainnet + null, // imaState.jo_token_manager, // for logs validation on s-chain + g_nTransferBlockSizeS2M, + g_nMaxTransactionsS2M, + g_nBlockAwaitDepthS2M, + g_nBlockAgeS2M, + fn_do_sign_messages_s2m, // fn_sign_messages or null + tc_main_net, + null // optsPendingTxAnalysis + ); + +The following code demonstrates cross-chain payments: + + IMA.do_payment_from_main_net( + g_w3_main_net, + joAccountMN, + joAccountSC, + g_jo_deposit_box, // only main net + g_chain_id_s_chain, + g_wei_amount // how much money to send + ); + await IMA.do_payment_from_s_chain( + g_w3_s_chain, + joAccountSC, + joAccountMN, + g_jo_token_manager, // only s-chain + g_wei_amount // how much money to send + ); diff --git a/src/README.md b/src/README.md new file mode 100644 index 00000000..fc031ac9 --- /dev/null +++ b/src/README.md @@ -0,0 +1,731 @@ + + +# SKALE Interchain Messaging Agent + +## Overview + +This article refers to **SKALE Interchain Messaging Agent** as **IMA**. + +**IMA** consists of the following parts: + + - Contracts on Mainnet + - Contracts on a SKALE Chain + - NodeJS based app + +## Contracts installation + +### Contracts prerequisites + +Get source code of Solidity contracts and install dependencies: + +```shell +git clone git@github.com:skalenetwork/IMA.git +cd ./IMA +``` + +### Node JS prerequisites + +Install required **Node JS** packages everywhere: + +```shell + export IMA_ROOT=..... + cd $IMA_ROOT + yarn install +``` + +Export required environment variables: + +```shell + export NETWORK_FOR_ETHEREUM="mainnet" + export PRIVATE_KEY_FOR_ETHEREUM="" + export NETWORK_FOR_SCHAIN="schain" + export PRIVATE_KEY_FOR_SCHAIN="" + export CHAIN_NAME_SCHAIN="Bob" + export URL_W3_ETHEREUM="http://127.0.0.1:8545" + export URL_W3_S_CHAIN="http://127.0.0.1:15000" + export ACCOUNT_FOR_ETHEREUM="" + export ACCOUNT_FOR_SCHAIN=""" +``` + +Notice: `ACCOUNT_FOR_ETHEREUM` address corresponds to `PRIVATE_KEY_FOR_ETHEREUM` private key, `ACCOUNT_FOR_SCHAIN` address corresponds to `PRIVATE_KEY_FOR_SCHAIN` private key. + +Rebuild all the contracts once to ensure everything initialized OK: + +```shell + cd $IMA_ROOT/proxy + npx hardhat clean && npx hardhat compile +``` + +### Contracts pre-installation on Mainnet and SKALE Chain + +For mainnet, invoke: + +```shell + cd $IMA_ROOT/proxy + yarn run deploy-to-mainnet + ls -1 ./data/ +``` + +You should see **proxyMainnet.json** file listed. + +For SKALE chain, invoke: + +```shell + cd $IMA_ROOT/proxy + yarn run deploy-to-schain + ls -1 ./data/ +``` + +You should see **proxySchain_*s-chain-name-here*.json** file listed. + +## IMA transaction signing + +**IMA** supports two ways of signing transactions: + +```shell + - Direct private key + - SGX Wallet +``` + +Private keys can be specified directly in **IMA** command line: + +```shell + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN +``` + +If **IMA** action needs to sign only **Main Net** transaction(s) and do read-only actions on **S-Chain**: + +```shell + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --address-s-chain=0x66c5a87f4a49dd75e970055a265e8dd5c3f8f852 +``` + +If **IMA** action needs to sign only **S-Chain** transaction(s) and do read-only actions on **Main Net**: + +```shell + --address-main-net=$ACCOUNT_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN \ +``` + +If **IMA** should use **SGX Wallet** to sign transactions, then the parameters above should be replaced with: + +```shell + --sgx-url-main-net=...\ + --sgx-url-s-chain=...\ + --sgx-ecdsa-key-main-net=...\ + --sgx-ecdsa-key-s-chain=...\ + --sgx-ssl-key-main-net=...\ + --sgx-ssl-key-s-chain=...\ + --sgx-ssl-cert-main-net=...\ + --sgx-ssl-cert-s-chain=...\ + --address-main-net=...\ + --address-s-chain=... +``` + +If **IMA** should use **Transaction Manager** to sign transactions, then the parameters above should be replaced with: + +```shell + --tm-url-main-net=...\ + --tm-url-s-chain=... \ + --address-main-net=...\ + --address-s-chain=... +``` + +**IMA** can use different ways of signing messages for **Main Net** and **S-Chain** by mixing connectivity parameters specified above. + +Where `--sgx-url-main-net` and `--sgx-url-s-chain` command line parameters provide **HTTPS** URLs for **SGX Wallets** for **Main Net** and **S-Chain**. These URLs can be equal. The `--sgx-ssl-key-main-net`, `--sgx-ssl-key-s-chain`, `--sgx-ssl-cert-main-net` and `--sgx-ssl-cert-s-chain` command line parameters provide SSL certificate and key files. The `--sgx-ecdsa-key-main-net` and `--sgx-ecdsa-key-s-chain` command line parameters provide registered name of **ECDSA key** in **SGX Wallets**, for example `NEK:000`. The `--address-main-net` and `--address-s-chain` command line parameters provide Ethereum wallet addresses corresponding to specified names of **ECDSA key** in **SGX Wallets**. + +It's possible to mix **SGX Wallet** and direct private key usage. I.e. **Main Net** and **S-Chain** can use different transaction signing ways. Nevertheless this is never needed in real life. + +## IMA installation + +### Bind IMA to Main-net + +You can check whether **IMA** is already bound with: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --check-registration \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN +``` + +**IMA** works as S-Chain extension. It should be registered on Main-net before performing any money transfers between blockchains: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --register \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN +``` + +### Run IMA transfer loop for particular S-Chain + +Performed with the **--loop** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --loop \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN +``` + +Notice: the command above can be run in forever while loop of shell script or became a part of daemon service file. + +### Gas reimbursement + +IMA transfers from **S-Chain** back to **Main Net** needs to be payed. Gas reimbursement feature allows to charge special wallet inside IMA for further transfer cost payments. + +Show balance: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --reimbursement-chain=Bob \ + --reimbursement-balance + --receiver=$ADDRESS_FOR_ETHEREUM +``` + +Estimate amount: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --reimbursement-chain=Bob \ + --reimbursement-estimate \ + --receiver=$ADDRESS_FOR_ETHEREUM +``` + +Recharge balance with 1 ETH: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --reimbursement-chain=Bob \ + --reimbursement-recharge=1eth +``` + +Withdraw 1 ETH: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --reimbursement-chain=Bob \ + --reimbursement-withdraw=1eth +``` + +Set minimal time range in seconds between **S-Chain** back to **Main Net** messages to `0` seconds(default value is `5` minutes): + +```shell + node ./main.js --verbose=9 --expose --colors \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_S_CHAIN \ + --reimbursement-range=0 +``` + +## Other IMA tasks + +### Getting command line help + +```shell + node ./main.js --colors --help +``` + +### Displaying run-time variables and arguments + +```shell + node ./main.js --colors --show-config +``` + +### Listing available output detail options + +```shell + node ./main.js --colors --verbose-list +``` + +### Specifying logging options + +Log output is always printed to standard output. Log also can be mirrored to file using **--log**=**path** command line option. + +By default mirrored log file grows with no limit and no log rotation is performed. To turn log rotation on and specify maximal size of log in bytes you should use the **--log-size**=**value** and **--log-files**=**value** command line options. + +## Asset transfer commands + +### Money transfer from Main-net account to S-Chain + +Performed with the **--m2s-payment** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --m2s-payment \ + --ether=1 \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --address-s-chain=$ACCOUNT_FOR_SCHAIN +``` + +Notice: The command above does payment from Main-net and that is why we need to specify private key for source account inside Main-net blockchain using the **--key-main-net** command line argument. Target S-chain account is specified as address with the **--address-s-chain** command line argument. We don't need to specify private key for target account. + +### Money transfer from S-Chain account to Main-net + +Performed with the **--s2m-payment** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --s2m-payment \ + --ether=1 \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --address-main-net=$ACCOUNT_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN +``` + +Notice: The command above does payment from Main-net and that is why we need to specify private key for source account inside S-chain blockchain using the **--key-s-chain** command line argument. Target Main-net account is specified as address with the **--address-main-net** command line argument. We don't need to specify private key for target account. + +### View how much ETH you can receive from S-Chain account to Main-net + +Performed with the **--s2m-view** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --s2m-view \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --id-s-chain=Bob \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM +``` + +Notice: this operation is related to ETH transfers only. + +### Receive money transfer from S-Chain account to Main-net + +Performed with the **--s2m-receive** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --s2m-receive \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM +``` + +Notice: this operation is related to ETH transfers only. + +### Money amount specification for transfer operations + +Amount of money should be specified with one of the following command line options + +```shell + --value=moneySpec..............Amount of eth/finney/szabo/shannon/lovelace/babbage/wei/ to transfer. For instance "1ether" or "100000wei". + --wei=number...................Amount of wei to transfer. + --babbage=number...............Amount of babbage(wei*1000) to transfer. + --lovelace=number..............Amount of lovelace(wei*1000*1000) to transfer. + --shannon=number...............Amount of shannon(wei*1000*1000*1000) to transfer. + --szabo=number.................Amount of szabo(wei*1000*1000*1000*1000) to transfer. + --finney=number................Amount of finney(wei*1000*1000*1000*1000*1000) to transfer. + --ether=number.................Amount of ether(wei*1000*1000*1000*1000*1000*1000) to transfer. +``` + +### Single transfer loops + +Single transfer operations are similar to the **--loop** normal mode but perform single loop iteration and exit. + +#### Single transfer iteration from Main-net to S-chain + +Performed with the **--m2s-transfer** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --m2s-transfer \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN +``` + +#### Single transfer iteration from S-chain to Main-net + +Performed with the **--s2m-transfer** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --s2m-transfer \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN +``` + +#### Single bidirectional transfer iteration between Main-net and S-chain + +Performed with the **--transfer** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --transfer \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN +``` + +### Transfer loop parameters + + - --skip-dry-run - Skip dry run contract method calls. + - --ignore-dry-run - Ignore result of dry run contract method calls and continue execute. + - --dry-run - Use error results of dry run contract method calls as actual errors and stop execute. + - --m2s-transfer-block-size - Number of transactions in one block to use in money transfer loop from Main-net to S-chain. + - --s2m-transfer-block-size - Number of transactions in one block to use in money transfer loop from S-chain to Main-net. + - --s2s-transfer-block-size - Number of transactions in one block to use in money transfer loop from S-chain to S-chain. + - --transfer-block-size - Number of transactions in one block to use in both money transfer loops. + - --m2s-max-transactions - Maximal number of transactions to do in money transfer loop from Main-net to S-chain (0 is unlimited). + - --s2m-max-transactions - Maximal number of transactions to do in money transfer loop from S-chain to Main-net (0 is unlimited). + - --s2s-max-transactions - Maximal number of transactions to do in money transfer loop from S-chain to S-chain (0 is unlimited). + - --max-transactions - Maximal number of transactions to do in both money transfer loops (0 is unlimited). + - --m2s-await-blocks - Maximal number of blocks to wait to appear in blockchain before transaction from Main-net to S-chain (0 is no wait). + - --s2m-await-blocks - Maximal number of blocks to wait to appear in blockchain before transaction from S-chain to Main-net (0 is no wait). + - --s2s-await-blocks - Maximal number of blocks to wait to appear in blockchain before transaction from S-chain to S-chain (0 is no wait). + - --await-blocks - Maximal number of blocks to wait to appear in blockchain before transaction between both S-chain and Main-net (0 is no wait). + - --period - Transfer loop period(seconds). + - --node-number=value - S-Chain node number(zero based). + - --nodes-count=value - S-Chain nodes count. + - --time-framing=value - Specifies period(in seconds) for time framing. Zero means disable time framing. + - --time-gap=value - Specifies gap(in seconds) before next time frame. + +### S-Chain specific configuration for more then one node S-Chains + +The **--node-number** and **--nodes-count** must me used for **IMA** instances running on S-Chain nodes which are part of multi-node S-Chain. + +### ERC20 transfer from Main-net account to S-Chain + +Performed with the **--m2s-payment** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --m2s-payment \ + --amount=1 \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --erc20-main-net=...path.../data-mn.json \ + --erc20-s-chain=...path.../data-sc.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --address-s-chain=0x66c5a87f4a49dd75e970055a265e8dd5c3f8f852 +``` + +Notice: The command above does payment from Main-net and that is why we need to specify private key for source account inside Main-net blockchain using the **--key-main-net** command line argument. Target S-chain account is specified as address with the **--address-s-chain** command line argument. We don't need to specify private key for target account. + +### ERC721 transfer from Main-net account to S-Chain + +Same as ERC20 above. But use **721** instead of **20** in command names. Also use **--tid** to specify ERC721 token id to send instead of **--amount**. + +### ERC1155 transfer from Main-net account to S-Chain + +Same as ERC20 above. But use **1155** instead of **20** in command names. Also use **--tid** to specify ERC1155 token id to send withing **--amount** of ERC1155 tokens. + +### ERC1155 batch transfer from Main-net account to S-Chain + +Similar to ERC1155 single token transfer.Use **--tids** to specify ERC1155 token ids in form of array `[1,2,3]` to send withing **--amounts** as array `[100,200,300]` with number of appropriate ERC1155 tokens to sent. + +### ERC20 transfer from S-Chain account to Main-net + +Performed with the **--s2m-payment** command line option: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --s2m-payment \ + --amount=1 \ + --sleep-between-tx=5000 \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --erc20-main-net=...path.../data-mn.json \ + --erc20-s-chain=...path.../data-sc.json \ + --address-main-net=$ACCOUNT_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN +``` + +Notice: The command above does payment from Main-net and that is why we need to specify private key for source account inside S-chain blockchain using the **--key-s-chain** command line argument. Target Main-net account is specified as address with the **--address-main-net** command line argument. We don't need to specify private key for target account. + +### ERC721 transfer from S-Chain account to Main-net + +Same as above. But use **721** instead of **20** in command names. Also use **--tid** to specify ERC721 token id to send instead of **--amount**. + +### ERC1155 transfer from S-Chain account to Main-net + +Same as ERC20 above. But use **1155** instead of **20** in command names. Also use **--tid** to specify ERC1155 token id to send withing **--amount** of ERC1155 tokens. + +### ERC1155 batch transfer from S-Chain account to Main-net + +Similar to ERC1155 single token transfer.Use **--tids** to specify ERC1155 token ids in form of array `[1,2,3]` to send withing **--amounts** as array `[100,200,300]` with number of appropriate ERC1155 tokens to sent. + +## Other options and commands + +### Show asset balances and owners + +You can asl **IMA Agent** to show ETH and, optionally, various token balances and/or ERC721 token owners: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --show-balance \ + --tids="[1,2,3] \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --address-main-net=$ACCOUNT_FOR_ETHEREUM \ + --address-s-chain=$ACCOUNT_FOR_SCHAIN \ + --erc20-main-net=.....path-to.....ERC20.abi.mn.json \ + --erc20-s-chain=.....path-to.....ERC20.abi.sc00.json \ + --erc721-main-net=.....path-to.....ERC721.abi.mn.json \ + --erc721-s-chain=.....path-to.....ERC721.abi.sc00.json \ + --erc1155-main-net=.....path-to.....ERC1155.abi.mn.json \ + --erc1155-s-chain=.....path-to.....ERC1155.abi.sc00.json +``` + +Example command above will always show: real ETH on Main Net, ETH user can receive on Main Net, real ETH on S-Chain, stored as ERC20 and local S-Chain ETH. + +The token balances and owners display is optional and depends on set of ABI and token IDs (`--tids`) arguments provided. + +### Browse S-Chain network + +You can ask agent app to scan **S-Chain** network information and parameters, print it and exit: + +```shell + node ./main.js --verbose=9 --expose --colors \ + --url-s-chain=$URL_W3_S_CHAIN --browse-s-chain +``` + +This information is used to sign messages on all **S-Chain** nodes. + +### Sign messages + +Message signing performed only for message sent from **S-Chain** to **MainNet**. + +Adding **--sign-messages** command line parameter turns on **BLS message signing** algorithm. +Agent app will scan **S-Chain** network and ask each of nodes to sign messages transferred from **MainNet** to **S-Chain**. +This options requires all **S-Chain** nodes to be configured with **SGX Wallet** or **Emu Wallet** access information. + +The **--bls-glue** command line parameter must be used to specify path to the **bls_glue** application. +This parameter must be specified if **--sign-messages** parameter is present. + +The **--bls-verify** command line parameter must be used to specify path to the **verify_bls** application. +This parameter is optional. If it was specified, then **IMA Agent** application will verify gathered BLS signatures. + +The **--hash-g1** command line parameter must be used to specify path to the **hash_g1** application. + +Message signing will work only on **S-Chain** where each **skaled** node configured properly and able to: + + - provide brows information for entire **S-Chain** network + - provide **IMA** signing APIs and parameters + +Here is example of correct **config.json** file for **skaled** node: + +```json +"skaleConfig": { + "nodeInfo": { + "nodeName": "???????????", "nodeID": ????, + "bindIP": "??.??.??.??, "bindIP6": "???????????", "basePort": ????, "basePort6": ????, + "logLevel": "trace", "logLevelProposal": "trace", + "emptyBlockIntervalMs": ?????, "ipc": false, "ipcpath": "./ipcx", "db-path": "./node", + "httpRpcPort": ????, "httpsRpcPort": ????, "wsRpcPort": ????, "wssRpcPort": ????, + "httpRpcPort6": ????, "httpsRpcPort6": ????, "wsRpcPort6": ????, "wssRpcPort6": ????, + "acceptors": 1, "max-connections": 0, + "web3-trace": true, "enable-debug-behavior-apis": false, "unsafe-transactions": false, + "aa": "always", "web3-shutdown": false, + "imaMainNet": "????://??.??.??.??:????", + "imaMessageProxySChain": "0x????????????????????????????????????????, + "imaMessageProxyMainNet": "0x????????????????????????????????????????", + "imaCallerAddressSChain": "0x????????????????????????????????????????", + "imaCallerAddressMainNet": "0x????????????????????????????????????????", + "wallets": { + "ima": { + "url": ""????://??.??.??.??:????" "keyShareName": "???????????????", "t": 2, "n": 2, + "BLSPublicKey??????????????????????????????????????????????????????????????????????????", + "BLSPublicKey??????????????????????????????????????????????????????????????????????????", + "BLSPublicKey??????????????????????????????????????????????????????????????????????????", + "BLSPublicKey??????????????????????????????????????????????????????????????????????????", + "commonBLSPublicKey??????????????????????????????????????????????????????????????????????????", + "commonBLSPublicKey??????????????????????????????????????????????????????????????????????????", + "commonBLSPublicKey??????????????????????????????????????????????????????????????????????????", + "commonBLSPublicKey??????????????????????????????????????????????????????????????????????????" + } + } + }, + "sChain": { + "schainID": 1234, "schainName": "????????????????????", + "nodes": [ { + "schainIndex": 1, "nodeID": ????, + "ip": "??.??.??.??", "ip6": "????????????????????", "basePort": ????, "basePort6": ????, + "httpRpcPort": ????, "httpsRpcPort": ????, "wsRpcPort": ????, "wssRpcPort": ????, + "httpRpcPort6": ????, "httpsRpcPort6": ????, "wsRpcPort6": ????, "wssRpcPort6": ???? + }, { + "schainIndex": 1, "nodeID": ????, + "ip": "??.??.??.??", "ip6": "????????????????????", "basePort": ????, "basePort6": ????, + "httpRpcPort": ????, "httpsRpcPort": ????, "wsRpcPort": ????, "wssRpcPort": ????, + "httpRpcPort6": ????, "httpsRpcPort6": ????, "wsRpcPort6": ????, "wssRpcPort6": ???? + } ] + } +} +``` + +Here is example of IMA message processing loop invocation with BLS support: + +```shell + reset; node ./main.js --verbose=9 --expose --colors \ + --loop \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN \ + --sign-messages \ + --bls-glue=...path.../bls_glue \ + --hash-g1=...path.../hash_g1 \ + --bls-verify=...path.../verify_bls + + reset; node ./main.js --verbose=9 --expose --colors \ + --loop \ + --url-main-net=$URL_W3_ETHEREUM \ + --url-s-chain=$URL_W3_S_CHAIN \ + --id-main-net=Mainnet \ + --id-s-chain=Bob \ + --cid-main-net=-4 \ + --cid-s-chain=-4 \ + --abi-main-net=../proxy/data/proxyMainnet.json \ + --abi-s-chain=../proxy/data/proxySchain_Bob.json \ + --key-main-net=$PRIVATE_KEY_FOR_ETHEREUM \ + --key-s-chain=$PRIVATE_KEY_FOR_SCHAIN \ + --sign-messages \ + --bls-glue=/home/serge/Work/skaled/build/libconsensus/libBLS/bls_glue \ + --hash-g1=/home/serge/Work/skaled/build/libconsensus/libBLS/hash_g1 \ + --bls-verify=/home/serge/Work/skaled/build/libconsensus/libBLS/verify_bls +``` + +### Gas computation and transaction customization + +All transactions performed by **IMA Agent** use estimated gas and dry-run. You may multiply estimated gas by `2` in sent transactions by specifying `--gas-multiplier=2` command line option. Per-chain independent options `--gas-multiplier-mn` and `gas-multiplier-sc` allow to use different gas multipliers. Similar command line options are provided to multiply gas price returned by blockchain: `--gas-price-multiplier`, `--gas-price-multiplier-mn` and `--gas-price-multiplier-sc`. diff --git a/src/SNB.md b/src/SNB.md new file mode 100644 index 00000000..a4b24fec --- /dev/null +++ b/src/SNB.md @@ -0,0 +1,177 @@ +# SKALE NETWORK BROWSER + +## General Description + +**SKALE Network Browser** (**SNB**) or **SKALE Observer** is part of IMA responsible for providing description of all SKALE chains. This is done via set of calls to **SKALE Manager**. + +**SNB** maintains cache of S-Chain descriptions and refreshes all descriptions periodically. These descriptions and needed for S-Chain to S-Chain IMA message transfers when IMA needs to know how to connect to other S-Chain. + +First SKALE network scan is performed by **SNB** on IMA startup. Next network description refreshes are performed periodically. + +**SNB** works can work in parallel thread to avoid any delay of main IMA's message transfer loop. + +## Implementation details + +The `SchainsInternal.numberOfSchains` contract call returns number of created S-Chains to load from **SKALE Manager**. For each of S-Chains we need to get its hash by index what is done `SchainsInternal.schainsAtSystem` contract call. Then contract call to `SchainsInternal.schains` returns basic S-Chain description by hash. Obtained basic S-Chain description does not describe nodes and they must be loaded via set of contract calls. The `SchainsInternal.getNodesInGroup` contract call returns array of node identifiers for all S-Chain nodes. The `Nodes.nodes` contract call returns node description by node id. Returned node description includes IP address, domain name and, base port of a node, maintenance state flag. Then call to `SchainsInternal.getSchainHashesForNode` contract call allows to find effective node base port and compute per-protocol ports (`http`, `https`, `ws`, `wss`). + +Cache of S-Chain descriptions is result of download process described above. When new S-Chain descriptions are downloaded, they replace old ones. By default this is performed once in an hour. + +S-Chain descriptions directly affect on S-Chain to S-Chain transfers because they contain JSON RPC URLs of all `skaled`s of all S-Chains. + +**SNB** can be invoked from command line of IMA agent using `--browse-skale-network` or `--browse-connected-schains` command line options. The`--browse-skale-network` command line options invokes download entire SKALE network description, all S-Chains, all `skaled` nodes. The `--browse-connected-schains` command line options invokes download of which are S-Chains connected to S-Chain with name specified in `--id-s-chain` command line parameter. + +Example of **SNB** invocation: + +```shell +node agent/main.js --colors --browse-skale-network \ + --abi-skale-manager=.../sm.json \ + --abi-main-net=.../mn.json \ + --abi-s-chain=.../sc.json \ + --url-main-net=http://127.0.0.1:8545 \ + --url-s-chain=http://127.0.0.1:15000 \ + --id-main-net=Mainnet \ + --id-s-chain=Bob1000 \ + --cid-main-net=456 \ + --cid-s-chain=1000 \ + --key-main-net=... \ + --key-s-chain=... +``` + +Example of downloaded S-Chains description containing 2 S-Chains named `Bob1000` and `Bob1001`, 2 `skaled` nodes each: + +```json +[ + { + "data":{ + "name":"Bob1000", + "owner":"0x7aa5E36AA15E93D10F4F26357C30F052DacDde5F", + "indexInOwnerList":"0", + "partOfNode":"0", + "lifetime":"5", + "startDate":"1641992759", + "startBlock":"249", + "deposit":"100000000000000000000", + "index":"0", + "generation":"0", + "originator":"0x0000000000000000000000000000000000000000", + "computed":{ + "ports":{ + "httpRpcPort":2264, + "httpsRpcPort":2269, + "wsRpcPort":2263, + "wssRpcPort":2268, + "infoHttpRpcPort":2270 + }, + "schain_id":"0x975a4814cff8b9fd85b48879dade195028650b0a23f339ca81bd3b1231f72974", + "chainId":"0x975a4814cff8b9", + "nodes":[ + { + "id":"0", + "name":"Aldo", + "ip":"127.0.0.1", + "base_port":"2161", + "domain":"test.domain.name.here", + "isMaintenance":false, + "schain_base_port":2161, + "http_endpoint_ip":"http://127.0.0.1:2164", + "https_endpoint_ip":"https://127.0.0.1:2169", + "ws_endpoint_ip":"ws://127.0.0.1:2163", + "wss_endpoint_ip":"wss://127.0.0.1:2168", + "info_http_endpoint_ip":"http://127.0.0.1:2170", + "http_endpoint_domain":"http://test.domain.name.here:2164", + "https_endpoint_domain":"https://test.domain.name.here:2169", + "ws_endpoint_domain":"ws://test.domain.name.here:2163", + "wss_endpoint_domain":"wss://test.domain.name.here:2168", + "info_http_endpoint_domain":"http://test.domain.name.here:2170" + }, + { + "id":"1", + "name":"Bear", + "ip":"127.0.0.2", + "base_port":"2261", + "domain":"test.domain.name.here", + "isMaintenance":false, + "schain_base_port":2261, + "http_endpoint_ip":"http://127.0.0.2:2264", + "https_endpoint_ip":"https://127.0.0.2:2269", + "ws_endpoint_ip":"ws://127.0.0.2:2263", + "wss_endpoint_ip":"wss://127.0.0.2:2268", + "info_http_endpoint_ip":"http://127.0.0.2:2270", + "http_endpoint_domain":"http://test.domain.name.here:2264", + "https_endpoint_domain":"https://test.domain.name.here:2269", + "ws_endpoint_domain":"ws://test.domain.name.here:2263", + "wss_endpoint_domain":"wss://test.domain.name.here:2268", + "info_http_endpoint_domain":"http://test.domain.name.here:2270" + } + ] + } + } + }, + { + "data":{ + "name":"Bob1001", + "owner":"0x7aa5E36AA15E93D10F4F26357C30F052DacDde5F", + "indexInOwnerList":"1", + "partOfNode":"0", + "lifetime":"5", + "startDate":"1641992767", + "startBlock":"260", + "deposit":"100000000000000000000", + "index":"1", + "generation":"0", + "originator":"0x0000000000000000000000000000000000000000", + "computed":{ + "ports":{ + "httpRpcPort":2364, + "httpsRpcPort":2369, + "wsRpcPort":2363, + "wssRpcPort":2368, + "infoHttpRpcPort":2370 + }, + "schain_id":"0xde9b5e1c7bac0a60f917397dfab6ead3f6441acf0399ec81145568874dd829e9", + "chainId":"0xde9b5e1c7bac0a", + "nodes":[ + { + "id":"3", + "name":"Seed", + "ip":"127.0.0.4", + "base_port":"2461", + "domain":"test.domain.name.here", + "isMaintenance":false, + "schain_base_port":2461, + "http_endpoint_ip":"http://127.0.0.4:2464", + "https_endpoint_ip":"https://127.0.0.4:2469", + "ws_endpoint_ip":"ws://127.0.0.4:2463", + "wss_endpoint_ip":"wss://127.0.0.4:2468", + "info_http_endpoint_ip":"http://127.0.0.4:2470", + "http_endpoint_domain":"http://test.domain.name.here:2464", + "https_endpoint_domain":"https://test.domain.name.here:2469", + "ws_endpoint_domain":"ws://test.domain.name.here:2463", + "wss_endpoint_domain":"wss://test.domain.name.here:2468", + "info_http_endpoint_domain":"http://test.domain.name.here:2470" + }, + { + "id":"2", + "name":"John", + "ip":"127.0.0.3", + "base_port":"2361", + "domain":"test.domain.name.here", + "isMaintenance":false, + "schain_base_port":2361, + "http_endpoint_ip":"http://127.0.0.3:2364", + "https_endpoint_ip":"https://127.0.0.3:2369", + "ws_endpoint_ip":"ws://127.0.0.3:2363", + "wss_endpoint_ip":"wss://127.0.0.3:2368", + "info_http_endpoint_ip":"http://127.0.0.3:2370", + "http_endpoint_domain":"http://test.domain.name.here:2364", + "https_endpoint_domain":"https://test.domain.name.here:2369", + "ws_endpoint_domain":"ws://test.domain.name.here:2363", + "wss_endpoint_domain":"wss://test.domain.name.here:2368", + "info_http_endpoint_domain":"http://test.domain.name.here:2370" + } + ] + } + } + } +] +``` diff --git a/src/about.txt b/src/about.txt new file mode 100644 index 00000000..1dc79a22 --- /dev/null +++ b/src/about.txt @@ -0,0 +1,214 @@ +IMA AGENT version 2.1.0 +GENERAL options: + --help..................................Show this help info and exit. + --version...............................Show version info and exit. + --colors................................Use ANSI-colorized logging. + --no-colors.............................Use monochrome logging. +BLOCKCHAIN NETWORK options: + --url-main-net=URL......................Main-net URL. Value is automatically loaded from the URL_W3_ETHEREUM environment variable if not specified. + --url-s-chain=URL.......................S-chain URL. Value is automatically loaded from the URL_W3_S_CHAIN environment variable if not specified. + --url-t-chain=URL.......................S<->S Target S-chain URL. Value is automatically loaded from the URL_W3_S_CHAIN_TARGET environment variable if not specified. + --id-main-net=number....................Main-net Ethereum network name. Value is automatically loaded from the CHAIN_NAME_ETHEREUM environment variable if not specified. Default value is "Mainnet". + --id-s-chain=number.....................S-chain Ethereum network name. Value is automatically loaded from the CHAIN_NAME_SCHAIN environment variable if not specified. Default value is "id-S-chain". + --id-t-chain=number.....................S<->S Target S-chain Ethereum network name. Value is automatically loaded from the CHAIN_NAME_SCHAIN_TARGET environment variable if not specified. Default value is "id-T-chain". + --cid-main-net=number...................Main-net Ethereum chain ID Value is automatically loaded from the CID_ETHEREUM environment variable if not specified. Default value is -4. + --cid-s-chain=number....................S-chain Ethereum chain ID Value is automatically loaded from the CID_SCHAIN environment variable if not specified. Default value is -4. + --cid-t-chain=number....................S<->S Target S-chain Ethereum chain ID Value is automatically loaded from the CID_SCHAIN_TARGET environment variable if not specified. Default value is -4. +BLOCKCHAIN INTERFACE options: + --abi-skale-manager=path................Path to JSON file containing Skale Manager ABI. Optional parameter. It's needed for S-Chain to S-Chain transfers. + --abi-main-net=path.....................Path to JSON file containing IMA ABI for Main-net. + --abi-s-chain=path......................Path to JSON file containing IMA ABI for S-chain. + --abi-t-chain=path......................Path to JSON file containing IMA ABI for S<->S Target S-chain. +ERC20 INTERFACE options: + --erc20-main-net=path...................Path to JSON file containing ERC20 ABI for Main-net. + --erc20-s-chain=path....................Path to JSON file containing ERC20 ABI for S-chain. + --addr-erc20-s-chain=address............Explicit ERC20 address in S-chain. + --erc20-t-chain=path....................Path to JSON file containing ERC20 ABI for S<->S Target S-chain. + --addr-erc20-t-chain=address............Explicit ERC20 address in S<->S Target S-chain. +ERC721 INTERFACE options: + --erc721-main-net=path..................Path to JSON file containing ERC721 ABI for Main-net. + --erc721-s-chain=path...................Path to JSON file containing ERC721 ABI for S-chain. + --addr-erc721-s-chain=address...........Explicit ERC721 address in S-chain. + --erc721-t-chain=path...................Path to JSON file containing ERC721 ABI for S<->S S-chain. + --addr-erc721-t-chain=address...........Explicit ERC721 address in S<->S S-chain. +ERC1155 INTERFACE options: + --erc1155-main-net=path.................Path to JSON file containing ERC1155 ABI for Main-net. + --erc1155-s-chain=path..................Path to JSON file containing ERC1155 ABI for S-chain. + --addr-erc1155-s-chain=address..........Explicit ERC1155 address in S-chain. + --erc1155-t-chain=path..................Path to JSON file containing ERC1155 ABI for S<->S S-chain. + --addr-erc1155-t-chain=address..........Explicit ERC1155 address in S<->S S-chain. +USER ACCOUNT options: + --tm-url-main-net=URL...................Transaction Manager server URL for Main-net Value is automatically loaded from the TRANSACTION_MANAGER_URL_ETHEREUM environment variable if not specified. Example: redis://@127.0.0.1:6379 + --tm-url-s-chain=URL....................Transaction Manager server URL for S-chain Value is automatically loaded from the TRANSACTION_MANAGER_URL_S_CHAIN environment variable if not specified. + --tm-url-t-chain=URL....................Transaction Manager server URL for S<->S Target S-chain Value is automatically loaded from the TRANSACTION_MANAGER_URL_S_CHAIN_TARGET environment variable if not specified. + --tm-priority-main-net=URL..............Transaction Manager priority for Main-net Value is automatically loaded from the TRANSACTION_MANAGER_PRIORITY_ETHEREUM environment variable if not specified. Default is 5. + --tm-priority-s-chain=URL...............Transaction Manager priority for S-chain Value is automatically loaded from the TRANSACTION_MANAGER_PRIORITY_S_CHAIN environment variable if not specified. Default is 5. + --tm-priority-t-chain=URL...............Transaction Manager priority for S<->S Target S-chain Value is automatically loaded from the TRANSACTION_MANAGER_PRIORITY_S_CHAIN_TARGET environment variable if not specified. Default is 5. + --sgx-url-main-net=URL..................SGX server URL for Main-net Value is automatically loaded from the SGX_URL_ETHEREUM environment variable if not specified. + --sgx-url-s-chain=URL...................SGX server URL for S-chain Value is automatically loaded from the SGX_URL_S_CHAIN environment variable if not specified. + --sgx-url-t-chain=URL...................SGX server URL for S<->S Target S-chain. + --sgx-url=URL...........................SGX server URL for all chains. + --sgx-ecdsa-key-main-net=name...........SGX/ECDSA key name for Main-net Value is automatically loaded from the SGX_KEY_ETHEREUM environment variable if not specified. + --sgx-ecdsa-key-s-chain=name............SGX/ECDSA key name for S-chain Value is automatically loaded from the SGX_KEY_S_CHAIN environment variable if not specified. + --sgx-ecdsa-key-t-chain=name............SGX/ECDSA key name for S<->S Target S-chain Value is automatically loaded from the SGX_KEY_S_CHAIN_TARGET environment variable if not specified. + --sgx-ecdsa-key=name....................SGX/ECDSA key name for all chains. + --sgx-bls-key-main-net=name.............SGX/BLS key name for Main-net Value is automatically loaded from the BLS_KEY_ETHEREUM environment variable if not specified. + --sgx-bls-key-s-chain=name..............SGX/BLS key name for S-chain Value is automatically loaded from the BLS_KEY_S_CHAIN environment variable if not specified. + --sgx-bls-key-t-chain=name..............SGX/BLS key name for S<->S Target S-chain Value is automatically loaded from the BLS_KEY_S_CHAIN_TARGET environment variable if not specified. + --sgx-bls-key=name......................SGX/BLS key name for all chains. + --sgx-ssl-key-main-net=path.............Path to SSL key file for SGX wallet of Main-net Value is automatically loaded from the SGX_SSL_KEY_FILE_ETHEREUM environment variable if not specified. + --sgx-ssl-key-s-chain=path..............Path to SSL key file for SGX wallet of S-chain Value is automatically loaded from the SGX_SSL_KEY_FILE_S_CHAIN environment variable if not specified. + --sgx-ssl-key-t-chain=path..............Path to SSL key file for SGX wallet of S<->S Target S-chain Value is automatically loaded from the SGX_SSL_KEY_FILE_S_CHAIN_TARGET environment variable if not specified. + --sgx-ssl-key=path......................Path to SSL key file for SGX wallet of all chains. + --sgx-ssl-cert-main-net=path............Path to SSL certificate file for SGX wallet of Main-net Value is automatically loaded from the SGX_SSL_CERT_FILE_ETHEREUM environment variable if not specified. + --sgx-ssl-cert-s-chain=path.............Path to SSL certificate file for SGX wallet of S-chain Value is automatically loaded from the SGX_SSL_CERT_FILE_S_CHAIN environment variable if not specified. + --sgx-ssl-cert-t-chain=path.............Path to SSL certificate file for SGX wallet of S<->S Target S-chain Value is automatically loaded from the SGX_SSL_CERT_FILE_S_CHAIN_TARGET environment variable if not specified. + --sgx-ssl-cert=path.....................Path to SSL certificate file for all chains. + --address-main-net=value................Main-net user account address Value is automatically loaded from the ACCOUNT_FOR_ETHEREUM environment variable if not specified. + --address-s-chain=value.................S-chain user account address Value is automatically loaded from the ACCOUNT_FOR_SCHAIN environment variable if not specified. + --address-t-chain=value.................S<->S Target S-chain user account address Value is automatically loaded from the ACCOUNT_FOR_SCHAIN_TARGET environment variable if not specified. + --key-main-net=value....................Private key for Main-net user account address Value is automatically loaded from the PRIVATE_KEY_FOR_ETHEREUM environment variable if not specified. + --key-s-chain=value.....................Private key for S-Chain user account address Value is automatically loaded from the PRIVATE_KEY_FOR_SCHAIN environment variable if not specified. + --key-t-chain=value.....................Private key for S<->S Target S-Chain user account address Value is automatically loaded from the PRIVATE_KEY_FOR_SCHAIN_TARGET environment variable if not specified. + Please notice, IMA prefer to use transaction manager to sign blockchain transactions if --tm-url-main-net/--tm-url-s-chain command line values or TRANSACTION_MANAGER_URL_ETHEREUM/TRANSACTION_MANAGER_URL_S_CHAIN shell variables were specified. Next preferred option is SGX wallet which is used if --sgx-url-main-net/--sgx-url-s-chain command line values or SGX_URL_ETHEREUM/SGX_URL_S_CHAIN shell variables were specified. SGX signing also needs key name, key and certificate files. Finally, IMA attempts to use explicitly provided private key to sign blockchain transactions if --key-main-net/--key-s-chain command line values or PRIVATE_KEY_FOR_ETHEREUM/PRIVATE_KEY_FOR_SCHAIN shell variables were specified. +GENERAL TRANSFER options: + --value=numberunitName .................Amount of unitName to transfer, where unitName is well known Ethereum unit name like ether or wei. + --wei=number............................Amount of wei to transfer. + --babbage=number........................Amount of babbage(wei*1000) to transfer. + --lovelace=number.......................Amount of lovelace(wei*1000*1000) to transfer. + --shannon=number........................Amount of shannon(wei*1000*1000*1000) to transfer. + --szabo=number..........................Amount of szabo(wei*1000*1000*1000*1000) to transfer. + --finney=number.........................Amount of finney(wei*1000*1000*1000*1000*1000) to transfer. + --ether=number..........................Amount of ether(wei*1000*1000*1000*1000*1000*1000) to transfer. + --amount=number.........................Amount of tokens to transfer. + --tid=number............................ERC721 or ERC1155 to ken id to transfer. + --amounts=array of numbers..............ERC1155 to ken id to transfer in batch. + --tids=array of numbers.................ERC1155 to ken amount to transfer in batch. + --sleep-between-tx=number...............Sleep time (in milliseconds) between transactions during complex operations. + --wait-next-block.......................Wait for next block between transactions during complex operations. +S-CHAIN TO S-CHAIN TRANSFER options: + --s2s-enable ...........................Enables S-Chain to S-Chain transfers. Default mode. The abi-skale-manager path must be provided. + --s2s-disable ..........................Disables S-Chain to S-Chain transfers. + --network-browser-path=path.............Path to SKALE NETWORK browse data file in JSON format containing descriptions of S-Chains connected to this S-Chain. +PAYMENT TRANSACTION options: + --gas-price-multiplier-mn ..............Sets Gas Price Multiplier for Main Net transactions, Default value is 1.25. Specify value 0.0 to disable Gas Price Customization for Main Net. + --gas-price-multiplier-sc ..............Sets Gas Price Multiplier for S-Chain transactions, Default value is 0.0. + --gas-price-multiplier-tc ..............Sets Gas Price Multiplier for S<->S Target S-Chain transactions, Default value is 0.0. + --gas-price-multiplier .................Sets Gas Price Multiplier for both Main Net and S-Chain(s). + --gas-multiplier-mn ....................Sets Gas Value Multiplier for Main Net transactions, Default value is 1.25. Specify value 0.0 to disable Gas Price Customization for Main Net. + --gas-multiplier-sc ....................Sets Gas Value Multiplier S-Chain transactions, Default value is 1.25. + --gas-multiplier-tc ....................Sets Gas Value Multiplier for S<->S Target S-Chain transactions, Default value is 1.25. + --gas-multiplier .......................Sets Gas Value Multiplier for both Main Net and S-Chain(s). +REGISTRATION commands: + --register .............................Register(perform all steps). + --register1 ............................Perform registration step 1 - register S-Chain on Main-net. + --check-registration ...................Perform registration status check(perform all steps). + --check-registration1 ..................Perform registration status check step 1 - register S-Chain on Main-net. + --check-registration2 ..................Perform registration status check step 2 - register S-Chain in deposit box. + --check-registration3 ..................Perform registration status check step 3 - register Main-net's deposit box on S-Chain. +ACTION commands: + --show-config ..........................Show configuration values and exit. + --show-balance .........................Show ETH and/or token balances on Main-net and/or S-Chain and exit. + --m2s-payment ..........................Do one payment from Main-net user account to S-chain user account. + --s2m-payment ..........................Do one payment from S-chain user account to Main-net user account. + --s2m-receive ..........................Receive one payment from S-chain user account to Main-net user account(ETH only, receives all the ETH pending in transfer). + --s2m-view .............................View money amount user can receive as payment from S-chain user account to Main-net user account(ETH only, receives all the ETH pending in transfer). + --s2s-payment ..........................Do one payment from S-chain user account to other S-chain user account. + --s2s-forward ..........................Indicates S<->S transfer direction is forward. I.e. source S-chain is token minter and instantiator. This is default mode. + --s2s-reverse ..........................Indicates S<->S transfer direction is reverse. I.e. destination S-chain is token minter and instantiator. + --m2s-transfer .........................Do single message transfer loop from Main-net to S-chain. + --s2m-transfer .........................Do single message transfer loop from S-chain to Main-net. + --s2s-transfer .........................Do single message transfer loop from S-chain to S-chain. + --with-metadata ........................Makes ERC721 transfer using special version of Token Manager to transfer token metadata. + --transfer .............................Run single M<->S and, optionally, S->S transfer loop iteration. + --loop .................................Run M<->S and, optionally, S->S transfer loops in parallel threads. + --simple-loop ..........................Run M<->S and, optionally, S->S transfer loops in main thread only. +ADDITIONAL ACTION options: + --no-wait-s-chain ......................Do not wait until S-Chain is started. + --max-wait-attempts=value...............Max number of S-Chain call attempts to do while it became alive and sane. + --skip-dry-run .........................Skip dry run invocation before payed contract method calls. + --no-ignore-dry-run ....................Use error results of dry run contract method calls as actual errors and stop execute. + --m2s-transfer-block-size=value.........Number of transactions in one block to use in message transfer loop from Main-net to S-chain. Default is 4. + --s2m-transfer-block-size=value.........Number of transactions in one block to use in message transfer loop from S-chain to Main-net. Default is 4. + --s2s-transfer-block-size=value.........Number of transactions in one block to use in message transfer loop from S-chain to S-chain. Default is 4. + --transfer-block-size=value.............Number of transactions in one block to use in all message transfer loops. + --m2s-transfer-steps=value..............Maximal number of blocks to transfer at a job run from Main-net to S-chain. Value 0 is unlimited. Default is 8. + --s2m-transfer-steps=value..............Maximal number of blocks to transfer at a job run from S-chain to Main-net. Value 0 is unlimited. Default is 8. + --s2s-transfer-steps=value..............Maximal number of blocks to transfer at a job run from S-chain to S-chain. Value 0 is unlimited. Default is 8. + --transfer-steps=value..................Maximal number of blocks to transfer at a job run in all transfer loops. Value 0 is unlimited. + --m2s-max-transactions=number...........Maximal number of transactions to do in message transfer loop from Main-net to S-chain(0 is unlimited). Default is 0. + --s2m-max-transactions=number...........Maximal number of transactions to do in message transfer loop from S-chain to Main-net(0 is unlimited). Default is 0. + --s2s-max-transactions=number...........Maximal number of transactions to do in message transfer loop from S-chain to S-chain(0 is unlimited). Default is 0. + --max-transactions=number...............Maximal number of transactions to do in all message transfer loops(0 is unlimited). + --m2s-await-blocks=number...............Maximal number of blocks to wait to appear in blockchain before transaction from Main-net to S-chain(0 is no wait). Default is 0. + --s2m-await-blocks=number...............Maximal number of blocks to wait to appear in blockchain before transaction from S-chain to Main-net(0 is no wait). Default is 0. + --s2s-await-blocks=number...............Maximal number of blocks to wait to appear in blockchain before transaction from S-chain to S-chain(0 is no wait). Default is 0. + --await-blocks=number...................Maximal number of blocks to wait to appear in blockchain before transaction between both S-chain and Main-net(0 is no wait). + --m2s-await-time=seconds................Minimal age of transaction message(in seconds) before it will be transferred from Main-net to S-chain(0 is no wait). Default is 0. + --s2m-await-time=seconds................Minimal age of transaction message(in seconds) before it will be transferred from S-chain to Main-net(0 is no wait). Default is 0. + --s2s-await-time=seconds................Minimal age of transaction message(in seconds) before it will be transferred from S-chain to S-chain(0 is no wait). Default is 0. + --await-time=seconds....................Minimal age of transaction message(in seconds) before it will be transferred between both S-chain and Main-net(0 is no wait). + --period ...............................Transfer loop period(in seconds). + --node-number=value.....................S-Chain node number(0-based). + --nodes-count=value.....................S-Chain nodes count. + --time-framing=value....................Specifies period(in seconds) for time framing(0 to disable time framing). + --time-gap=value........................Specifies gap(in seconds) before next time frame. + --auto-exit=seconds.....................Automatically exit IMA Agent after specified number of seconds(0 is no automatic exit, 3600 is no default). +TOKEN TESTING commands: + --mint-erc20 ...........................Mint ERC20 tokens. + --mint-erc721 ..........................Mint ERC721 tokens. + --mint-erc1155 .........................Mint ERC1155 tokens. + --burn-erc20 ...........................Burn ERC20 tokens. + --burn-erc721 ..........................Burn ERC721 tokens. + --burn-erc1155 .........................Burn ERC1155 tokens. + Please notice, token testing commands require --tm-url-t-chain, cid-t-chain, erc20-t-chain or erc721-t-chain or erc1155-t-chain, account information (like private key key-t-chain) command line arguments specified. Token amounts are specified via amount command line arguments specified. Token IDs are specified via tid or tids command line arguments. +IMA WORK STATE ANALYSIS options: + --pwa ..................................Enable pending work analysis to avoid transaction conflicts. Default mode. + --no-pwa ...............................Disable pending work analysis. Not recommended for slow and overloaded blockchains. + --pwa-timeout=seconds...................Node state timeout during pending work analysis. Default is 60 seconds. +MESSAGE SIGNING options: + --sign-messages ........................Sign transferred messages. + --bls-glue=path.........................Specifies path to bls_glue application. + --hash-g1=path..........................Specifies path to hash_g1 application. + --bls-verify=path.......................Optional parameter, specifies path to verify_bls application. +MONITORING options: + --monitoring-port=number................Run monitoring web socket RPC server on specified port. Specify 0 to disable. By default monitoring server is disabled. + --monitoring-log........................Enable logging on monitoring web socket RPC server. By default these log messages are disabled. +GAS REIMBURSEMENT options: + --reimbursement-chain=name..............Specifies chain name. + --reimbursement-recharge=vu ............Recharge user wallet with specified value v, unit name u is well known Ethereum unit name like ether or wei. + --reimbursement-withdraw=vu ............Withdraw user wallet with specified value v, unit name u is well known Ethereum unit name like ether or wei. + --reimbursement-balance ................Show wallet balance. + --reimbursement-range=number............Sets minimal time interval between transfers from S-Chain to Main Net. +PAST EVENTS SCAN options: + --bs-step-size=number...................Specifies step block range size to search iterative past events step by step. 0 to disable iterative search. + --bs-max-all-range=number ..............Specifies max number of steps to allow to search as [0...latest] range. 0 to disable iterative search. + --bs-progressive-enable ................Enables progressive block scan to search past events. + --bs-progressive-disable ...............Disables progressive block scan to search past events. +ORACLE BASED GAS REIMBURSEMENT options: + --enable-oracle ........................Enable call to Oracle to compute gas price for gas reimbursement. Default mode. + --disable-oracle .......................Disable call to Oracle to compute gas price for gas reimbursement. +IMA JSON RPC SERVER options: + --json-rpc-port=number..................Run IMA JSON RPC server on specified port. Specify 0 to disable. Default is 0. + --cross-ima ............................Enable calls to IMA JSON RPC servers to compute BLS signature parts and operation state inside time frames. Use calls to IMA Agent. + --no-cross-ima .........................Disable calls to IMA JSON RPC servers to compute BLS signature parts and operation state inside time frames. Use calls to skaled. Default mode. +TEST options: + --browse-s-chain .......................Download own S-Chain's network information. +LOGGING options: + --expose ...............................Expose low-level log details after successful operations. By default details exposed only on errors. + --no-expose ............................Expose low-level log details only after errors. Default expose mode. + --verbose=value.........................Set level of output details. + --verbose-list..........................List available verbose levels and exit. + --log=path..............................Write program output to specified log file(multiple files can be specified). + --log-size=value........................Max size(in bytes) of one log file(affects to log log rotation). + --log-files=value.......................Maximum number of log files for log rotation. + --gathered .............................Print details of gathering data from command line arguments. Default mode. + --no-gathered ..........................Do not print details of gathering data from command line arguments. + --expose-security-info .................Expose security-related values in log output. This mode is needed for debugging purposes only. + --no-expose-security-info ..............Do not expose security-related values in log output. Default mode. + --expose-pwa ...........................Expose IMA agent pending work analysis information + --no-expose-pwa ........................Do not expose IMA agent pending work analysis information. Default mode. + --accumulated-log-in-transfer ..........Use accumulated log in message transfer loop. + --accumulated-log-in-bls-signer ........Use accumulated log in BLS signer. + --dynamic-log-in-transfer ..............Use realtime log in message transfer loop. + --dynamic-log-in-bls-signer ............Use realtime log in BLS signer. diff --git a/src/bls.ts b/src/bls.ts new file mode 100644 index 00000000..4d7be1b7 --- /dev/null +++ b/src/bls.ts @@ -0,0 +1,2453 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file bls.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as fs from "fs"; +import * as path from "path"; +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as childProcessModule from "child_process"; +import * as rpcCall from "./rpcCall.js"; +import * as shellMod from "shelljs"; +import * as imaUtils from "./utils.js"; +import * as sha3Module from "sha3"; +import * as skaleObserver from "./observer.js"; +import * as discoveryTools from "./discoveryTools.js"; +import * as threadInfo from "./threadInfo.js"; +import * as utils from "./socketUtils.js"; +import * as state from "./state.js"; +import type * as loop from "./loop.js"; +import type * as IMA from "./imaCore.js"; + +export interface TQAInformation { + skaledNumber: number + sequenceId: string + ts: string +} + +export interface TSignResult { + errorMessage?: string | null + signatureShare?: string + status?: number + error?: string | null +} + +export interface TGatheringTracker { + nCountReceivedPrevious: number + nCountReceived: number + nCountErrors: number + nCountSkipped: number + nWaitIntervalStepMilliseconds: number + nWaitIntervalMaxSteps: number +} + +export interface TSignOperationOptions { + imaState: state.TIMAState + nTransferLoopCounter: number + strDirection: string + jarrMessages: any[] + nIdxCurrentMsgBlockStart: number + strFromChainName: string + joExtraSignOpts?: loop.TExtraSignOpts | null + fn: IMA.TFunctionAfterSigningMessages + bHaveResultReportCalled: boolean + strLogPrefix: string + strLogPrefixA: string + strLogPrefixB: string + joGatheringTracker: TGatheringTracker + arrSignResults: any[] + details: log.TLogger + strGatheredDetailsName: string + sequenceId: string + jarrNodes: discoveryTools.TSChainNode[] + nThreshold: number + nParticipants: number + nCountOfBlsPartsToCollect: number + errGathering: Error | string | null + targetChainName: string + fromChainName: string + targetChainID: string | number + fromChainID: string | number +} + +export interface TBSU256CallData { + params: { + valueToSign: string + qa?: TQAInformation + } +} + +export interface TBSU256Options { + joCallData: TBSU256CallData + imaState: state.TIMAState + strLogPrefix: string + details: log.TLogger + joRetVal: any | null + isSuccess: boolean + nThreshold: number + nParticipants: number + u256: any | null + strMessageHash: string + joAccount: state.TAccount | null + qa?: TQAInformation +} + +export interface THandleVerifyAndSignCallDataParams { + startMessageIdx: number + srcChainName: string + dstChainName: string + srcChainID: string + dstChainID: string + direction: string + messages: any[] + qa?: TQAInformation +} + +export interface THandleVerifyAndSignCallData { + params: THandleVerifyAndSignCallDataParams +} + +export interface THandleVerifyAndSignOptions { + joCallData: THandleVerifyAndSignCallData + imaState: state.TIMAState + strLogPrefix: string + details: log.TLogger + joRetVal: any + isSuccess: boolean + nIdxCurrentMsgBlockStart: number + strFromChainName: string + strToChainName: string + strFromChainID: string + strToChainID: string + strDirection: string + jarrMessages: any[] + strMessageHash: string + joExtraSignOpts: loop.TExtraSignOpts | null + nThreshold: number + nParticipants: number +} + +export interface TSignU256Options { + u256: any + fn: IMA.TFunctionAfterSigningMessages + details: log.TLogger + imaState: state.TIMAState + strLogPrefix: string + joGatheringTracker: { + nCountReceivedPrevious: number + nCountReceived: number + nCountErrors: number + nCountSkipped: number + nWaitIntervalStepMilliseconds: number + nWaitIntervalMaxSteps: number // 10 is 1 second + } + arrSignResults: any[] + jarrNodes: discoveryTools.TSChainNode[] + nThreshold: number + nParticipants: number + nCountOfBlsPartsToCollect: number + errGathering: Error | string | null +} + +const anyShellMod: any = shellMod as any; +const shell = anyShellMod.default; + +const Keccak = sha3Module.Keccak; + +function discoverBlsThreshold( joSChainNetworkInfo: discoveryTools.TSChainNetworkInfo ): number { + const imaState: state.TIMAState = state.get(); + joSChainNetworkInfo = joSChainNetworkInfo || imaState.joSChainNetworkInfo; + if( !joSChainNetworkInfo ) + return -1; + const jarrNodes = joSChainNetworkInfo.network; + for( let i = 0; i < jarrNodes.length; ++i ) { + const joNode = jarrNodes[i]; + if( discoveryTools.isSChainNodeFullyDiscovered( joNode ) ) + return joNode.imaInfo.t; + } + return -1; +} + +function discoverBlsParticipants( joSChainNetworkInfo: discoveryTools.TSChainNetworkInfo ): number { + const imaState: state.TIMAState = state.get(); + joSChainNetworkInfo = joSChainNetworkInfo || imaState.joSChainNetworkInfo; + if( !joSChainNetworkInfo ) + return -1; + const jarrNodes = joSChainNetworkInfo.network; + for( let i = 0; i < jarrNodes.length; ++i ) { + const joNode = jarrNodes[i]; + if( discoveryTools.isSChainNodeFullyDiscovered( joNode ) ) + return joNode.imaInfo.n; + } + return -1; +} + +function checkBlsThresholdAndBlsParticipants( + nThreshold: number, nParticipants: number, strOperation: string, details: log.TLogger +): boolean { + details = details || log; + if( nThreshold <= 0 ) { + details.fatal( "Operation {} will fail because discovered BLS threshold {}" + + " is invalid number or bad value", strOperation, nThreshold ); + return false; + } + if( nParticipants <= 0 ) { + details.fatal( "Operation {} will fail because discovered BLS number of participants {}" + + " is invalid number or bad value", strOperation, nParticipants ); + return false; + } + if( nThreshold > nParticipants ) { + details.fatal( "Operation {} will fail because discovered BLS threshold {} is greater " + + "than BLS number of participants {}", strOperation, nThreshold, nParticipants ); + return false; + } + return true; +} + +function discoverPublicKeyByIndex( + nNodeIndex: number, joSChainNetworkInfo: discoveryTools.TSChainNetworkInfo, + details: log.TLogger, isThrowException: boolean +): discoveryTools.TBLSPublicKey | null { + details = details || log; + const imaState: state.TIMAState = state.get(); + joSChainNetworkInfo = joSChainNetworkInfo || imaState.joSChainNetworkInfo; + const jarrNodes = joSChainNetworkInfo.network; + const cntNodes = jarrNodes.length; + const joNode = jarrNodes[nNodeIndex]; + if( discoveryTools.isSChainNodeFullyDiscovered( joNode ) ) { + return { + BLSPublicKey0: joNode.imaInfo.BLSPublicKey0, + BLSPublicKey1: joNode.imaInfo.BLSPublicKey1, + BLSPublicKey2: joNode.imaInfo.BLSPublicKey2, + BLSPublicKey3: joNode.imaInfo.BLSPublicKey3 + }; + } + details.fatal( "BLS 1/{} public key discovery failed for node #{}, node data is: {}", + cntNodes, nNodeIndex, joNode ); + if( isThrowException ) + throw new Error( `BLS 1/${cntNodes} public key discovery failed for node #${nNodeIndex}` ); + return null; +} + +function discoverCommonPublicKey( + details: log.TLogger, joSChainNetworkInfo: discoveryTools.TSChainNetworkInfo, + isThrowException: boolean ): discoveryTools.TBLSCommonPublicKey | null { + const imaState: state.TIMAState = state.get(); + joSChainNetworkInfo = joSChainNetworkInfo || imaState.joSChainNetworkInfo; + const jarrNodes = joSChainNetworkInfo.network; + for( let i = 0; i < jarrNodes.length; ++i ) { + const joNode = jarrNodes[i]; + if( discoveryTools.isSChainNodeFullyDiscovered( joNode ) ) { + return { + commonBLSPublicKey0: joNode.imaInfo.commonBLSPublicKey0, + commonBLSPublicKey1: joNode.imaInfo.commonBLSPublicKey1, + commonBLSPublicKey2: joNode.imaInfo.commonBLSPublicKey2, + commonBLSPublicKey3: joNode.imaInfo.commonBLSPublicKey3 + }; + } + } + details.fatal( "BLS common public key discovery failed, chain data is: {}", + joSChainNetworkInfo ); + if( isThrowException ) + throw new Error( "BLS common public key discovery failed" ); + return null; +} + +function hexPrepare( + strHex: string, isInvertBefore: boolean, isInvertAfter: boolean +): Uint8Array { + if( isInvertBefore == undefined ) + isInvertBefore = true; + if( isInvertAfter == undefined ) + isInvertAfter = true; + let arrBytes = imaUtils.hexToBytes( strHex ); + if( isInvertBefore ) + arrBytes = arrBytes.reverse(); + arrBytes = imaUtils.bytesAlignLeftWithZeroes( arrBytes, 32 ); + if( isInvertAfter ) + arrBytes = arrBytes.reverse(); + return arrBytes; +} + +function stringToKeccak256( s: string ): Uint8Array { + const strU256 = owaspUtils.ethersMod.ethers.utils.id( s ); + return hexPrepare( strU256, true, true ); +} + +function arrayToKeccak256( arrBytes: Uint8Array ): Uint8Array { + const k = new Keccak( 256 ); + k.update( imaUtils.toBuffer( arrBytes ) ); + const h = k.digest( "hex" ); + return imaUtils.hexToBytes( "0x" + h ); +} + +function keccak256Message( + jarrMessages: any[], nIdxCurrentMsgBlockStart: number, strFromChainName: string +): string { + let arrBytes = stringToKeccak256( strFromChainName ); + arrBytes = imaUtils.bytesConcat( + arrBytes, + hexPrepare( + owaspUtils.ensureStartsWith0x( nIdxCurrentMsgBlockStart.toString( 16 ) ), + false, false ) + ); + arrBytes = arrayToKeccak256( arrBytes ); + const cnt = jarrMessages.length; + for( let i = 0; i < cnt; ++i ) { + const joMessage = jarrMessages[i]; + let bytesSender = imaUtils.hexToBytes( joMessage.sender.toString() ); + bytesSender = imaUtils.bytesAlignLeftWithZeroes( bytesSender, 32 ); + arrBytes = imaUtils.bytesConcat( arrBytes, bytesSender ); + let bytesDestinationContract = imaUtils.hexToBytes( joMessage.destinationContract ); + bytesDestinationContract = + imaUtils.bytesAlignLeftWithZeroes( bytesDestinationContract, 32 ); + arrBytes = imaUtils.bytesConcat( arrBytes, bytesDestinationContract ); + const bytesData = imaUtils.hexToBytes( joMessage.data ); + arrBytes = imaUtils.bytesConcat( arrBytes, bytesData ); + arrBytes = arrayToKeccak256( arrBytes ); + } + return owaspUtils.ensureStartsWith0x( imaUtils.bytesToHex( arrBytes, false ) ); +} + +export function keccak256U256( u256: any, isHash: boolean ): string { + let arrBytes = new Uint8Array(); + let bytesU256 = imaUtils.hexToBytes( u256 ); + bytesU256 = bytesU256.reverse(); + bytesU256 = imaUtils.bytesAlignLeftWithZeroes( bytesU256, 32 ); + bytesU256 = bytesU256.reverse(); + arrBytes = imaUtils.bytesConcat( arrBytes, bytesU256 ); + let strMessageHash = ""; + if( isHash ) { + const hash = new Keccak( 256 ); + hash.update( imaUtils.toBuffer( arrBytes ) ); + strMessageHash = hash.digest( "hex" ); + } else + strMessageHash = "0x" + imaUtils.bytesToHex( arrBytes ); + return strMessageHash; +} + +export function keccak256ForPendingWorkAnalysis( + nNodeNumber: number, strLoopWorkType: string, isStart: boolean, ts: any +): string { + let arrBytes = new Uint8Array(); + + let bytesU256 = imaUtils.hexToBytes( nNodeNumber ); + bytesU256 = bytesU256.reverse(); + bytesU256 = imaUtils.bytesAlignLeftWithZeroes( bytesU256, 32 ); + bytesU256 = bytesU256.reverse(); + arrBytes = imaUtils.bytesConcat( arrBytes, bytesU256 ); + + arrBytes = imaUtils.bytesConcat( arrBytes, stringToKeccak256( strLoopWorkType ) ); + + bytesU256 = imaUtils.hexToBytes( isStart ? 1 : 0 ); + bytesU256 = bytesU256.reverse(); + bytesU256 = imaUtils.bytesAlignLeftWithZeroes( bytesU256, 32 ); + bytesU256 = bytesU256.reverse(); + arrBytes = imaUtils.bytesConcat( arrBytes, bytesU256 ); + + bytesU256 = imaUtils.hexToBytes( ts ); + bytesU256 = bytesU256.reverse(); + bytesU256 = imaUtils.bytesAlignLeftWithZeroes( bytesU256, 32 ); + bytesU256 = bytesU256.reverse(); + arrBytes = imaUtils.bytesConcat( arrBytes, bytesU256 ); + + const hash = new Keccak( 256 ); + hash.update( imaUtils.toBuffer( arrBytes ) ); + const strMessageHash = hash.digest( "hex" ); + return strMessageHash; +} + +function splitSignatureShare( signatureShare: any ): any { + const jarr = signatureShare.split( ":" ); + if( jarr.length < 2 ) + throw new Error( `Failed to split signatureShare=${signatureShare.toString()}` ); + return { X: jarr[0], Y: jarr[1] }; +} + +function getBlsGlueTmpDir(): string { + const strTmpDir = "/tmp/ima-bls-glue"; + shell.mkdir( "-p", strTmpDir ); + return strTmpDir; +} + +function allocBlsTmpActionDir(): string { + const strActionDir = getBlsGlueTmpDir() + "/" + imaUtils.replaceAll( imaUtils.uuid(), "-", "" ); + if( !fs.existsSync( strActionDir ) ) + fs.mkdirSync( strActionDir, { recursive: true } ); + return strActionDir; +} + +function performBlsGlue( + details: log.TLogger, strDirection: string, jarrMessages: any[], + nIdxCurrentMsgBlockStart: number, strFromChainName: string, arrSignResults: any[] +): any { + const imaState: state.TIMAState = state.get(); + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const strLogPrefix = `${strDirection}/BLS/Glue: `; + let joGlueResult: any = null; + const nThreshold = discoverBlsThreshold( imaState.joSChainNetworkInfo ); + const nParticipants = discoverBlsParticipants( imaState.joSChainNetworkInfo ); + details.debug( "{p}Discovered BLS threshold is {}.", strLogPrefix, nThreshold ); + details.debug( "{p}Discovered number of BLS participants is {}.", strLogPrefix, nParticipants ); + if( !checkBlsThresholdAndBlsParticipants( nThreshold, nParticipants, "BLS glue", details ) ) + return null; + const strMessageHash = owaspUtils.removeStarting0x( + keccak256Message( jarrMessages, nIdxCurrentMsgBlockStart, strFromChainName ) ); + details.debug( "{p}Message hash to sign is {}", strLogPrefix, strMessageHash ); + const strActionDir = allocBlsTmpActionDir(); + details.trace( "{p}{sunny} will work in {} director with {} sign results...", + strLogPrefix, "performBlsGlue", strActionDir, arrSignResults.length ); + const fnShellRestore = function(): void { shell.rm( "-rf", strActionDir ); }; + const strOutput = ""; + try { + let strInput = ""; + const cnt = arrSignResults.length; + for( let i = 0; i < cnt; ++i ) { + const jo: any = arrSignResults[i]; + if( !jo ) + throw new Error( `Failed to save BLS part ${i} because it's not JSON object` ); + const strPath = strActionDir + "/sign-result" + jo.index + ".json"; + details.trace( "{p}Saving {} file containing {}", strLogPrefix, strPath, jo ); + imaUtils.jsonFileSave( strPath, jo ); + strInput += " --input " + strPath; + } + const strGlueCommand = + imaState.strPathBlsGlue + + " --t " + nThreshold + + " --n " + nParticipants + + strInput + + " --output " + strActionDir + "/glue-result.json"; + details.trace( "{p}Will execute BLS glue command: {}", strLogPrefix, strGlueCommand ); + let strOutput = childProcessModule.execSync( strGlueCommand, { cwd: strActionDir } ); + details.trace( "{p}BLS glue output is:\n{raw}", strLogPrefix, strOutput || "<>" ); + joGlueResult = imaUtils.jsonFileLoad( path.join( strActionDir, "glue-result.json" ) ); + details.trace( "{p}BLS glue result is: {}", strLogPrefix, joGlueResult ); + if( joGlueResult && "X" in joGlueResult.signature && "Y" in joGlueResult.signature ) { + details.success( "{p}BLS glue success", strLogPrefix ); + joGlueResult.hashSrc = strMessageHash; + details.trace( "{p}Computing G1 hash point...", strLogPrefix ); + const strPath = strActionDir + "/hash.json"; + details.trace( "{p}Saving {} file...", strLogPrefix, strPath ); + imaUtils.jsonFileSave( strPath, { message: strMessageHash } ); + const strHasG1Command = + imaState.strPathHashG1 + + " --t " + nThreshold + + " --n " + nParticipants; + details.trace( "{p}Will execute HashG1 command {}", strLogPrefix, strHasG1Command ); + strOutput = childProcessModule.execSync( strHasG1Command, { cwd: strActionDir } ); + details.trace( "{p}HashG1 output is:\n{raw}", strLogPrefix, strOutput || "<>" ); + const joResultHashG1 = imaUtils.jsonFileLoad( path.join( strActionDir, "g1.json" ) ); + details.trace( "{p}HashG1 result is: {}", strLogPrefix, joResultHashG1 ); + if( "g1" in joResultHashG1 && + "hint" in joResultHashG1.g1 && + "hashPoint" in joResultHashG1.g1 && + "X" in joResultHashG1.g1.hashPoint && + "Y" in joResultHashG1.g1.hashPoint + ) { + joGlueResult.hashPoint = joResultHashG1.g1.hashPoint; + joGlueResult.hint = joResultHashG1.g1.hint; + } else { + joGlueResult = null; + throw new Error( `malformed HashG1 result: ${JSON.stringify( joResultHashG1 )}` ); + } + } else { + const joSavedGlueResult = joGlueResult; + joGlueResult = null; + throw new Error( `malformed BLS glue result: ${JSON.stringify( joSavedGlueResult )}` ); + } + fnShellRestore(); + } catch ( err ) { + details.critical( "{p}BLS glue error description is: {err}, stack is: \n{stack}", + strLogPrefix, err, err ); + details.critical( "{p}BLS glue output is:\n{raw}", strLogPrefix, strOutput || "<>" ); + fnShellRestore(); + joGlueResult = null; + } + return joGlueResult; +} + +function performBlsGlueU256( details: log.TLogger, u256: any, arrSignResults: any[] ): any { + const imaState: state.TIMAState = state.get(); + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const strLogPrefix = "BLS/Glue: "; + let joGlueResult: any = null; + const nThreshold = discoverBlsThreshold( imaState.joSChainNetworkInfo ); + const nParticipants = discoverBlsParticipants( imaState.joSChainNetworkInfo ); + details.debug( "{p}Discovered BLS threshold is {}.", strLogPrefix, nThreshold ); + details.debug( "{p}Discovered number of BLS participants is {}.", strLogPrefix, nParticipants ); + if( !checkBlsThresholdAndBlsParticipants( nThreshold, nParticipants, "BLS glue-256", details ) ) + return null; + details.trace( "{p}Original long message is {}", strLogPrefix, keccak256U256( u256, false ) ); + const strMessageHash = keccak256U256( u256, true ); + details.trace( "{p}Message hash to sign is {}", strLogPrefix, strMessageHash ); + const strActionDir = allocBlsTmpActionDir(); + details.trace( "{p}performBlsGlueU256 will work in {} director with {} sign results...", + strLogPrefix, strActionDir, arrSignResults.length ); + const fnShellRestore = function(): void { shell.rm( "-rf", strActionDir ); }; + let strOutput = ""; + try { + let strInput = ""; + const cnt = arrSignResults.length; + for( let i = 0; i < cnt; ++i ) { + const jo: any = arrSignResults[i]; + if( !jo ) + throw new Error( `Failed to save BLS part ${i} because it's not JSON object` ); + const strPath = strActionDir + "/sign-result" + jo.index + ".json"; + details.trace( "{p}Saving {} file...", strLogPrefix, strPath ); + imaUtils.jsonFileSave( strPath, jo ); + strInput += " --input " + strPath; + } + const strGlueCommand = + imaState.strPathBlsGlue + + " --t " + nThreshold + + " --n " + nParticipants + + strInput + + " --output " + strActionDir + "/glue-result.json"; + details.trace( "{p}Will execute BLS glue command: {}", strLogPrefix, strGlueCommand ); + strOutput = + childProcessModule.execSync( strGlueCommand, { cwd: strActionDir } ).toString( "utf8" ); + details.trace( "{p}BLS glue output is:\n{raw}", strLogPrefix, strOutput || "<>" ); + joGlueResult = imaUtils.jsonFileLoad( path.join( strActionDir, "glue-result.json" ) ); + details.trace( "{p}BLS glue result is:\n{}", strLogPrefix, joGlueResult ); + if( joGlueResult && "X" in joGlueResult.signature && "Y" in joGlueResult.signature ) { + details.success( "{p}BLS glue success", strLogPrefix ); + joGlueResult.hashSrc = strMessageHash; + details.trace( "{p}Computing G1 hash point...", strLogPrefix ); + const strPath = strActionDir + "/hash.json"; + details.trace( "{p}Saving {} file...", strLogPrefix, strPath ); + imaUtils.jsonFileSave( strPath, { message: strMessageHash } ); + const strHasG1Command = + imaState.strPathHashG1 + + " --t " + nThreshold + + " --n " + nParticipants; + details.trace( "{p}Will execute HashG1 command: {}", strLogPrefix, strHasG1Command ); + strOutput = childProcessModule.execSync( strHasG1Command, { cwd: strActionDir } ) + .toString( "utf8" ); + details.trace( "{p}HashG1 output is:\n{raw}", strLogPrefix, strOutput || "<>" ); + const joResultHashG1 = imaUtils.jsonFileLoad( path.join( strActionDir, "g1.json" ) ); + details.trace( "{p}HashG1 result is: {}", strLogPrefix, joResultHashG1 ); + if( "g1" in joResultHashG1 && + "hint" in joResultHashG1.g1 && + "hashPoint" in joResultHashG1.g1 && + "X" in joResultHashG1.g1.hashPoint && + "Y" in joResultHashG1.g1.hashPoint + ) { + joGlueResult.hashPoint = joResultHashG1.g1.hashPoint; + joGlueResult.hint = joResultHashG1.g1.hint; + } else { + joGlueResult = null; + throw new Error( `malformed HashG1 result: ${JSON.stringify( joResultHashG1 )}` ); + } + } else { + const joSavedGlueResult = joGlueResult; + joGlueResult = null; + throw new Error( `malformed BLS glue result: ${JSON.stringify( joSavedGlueResult )}` ); + } + fnShellRestore(); + } catch ( err ) { + details.critical( "BLS glue error description is: {err}, stack is: \n{stack}", + err, err ); + details.critical( "BLS glue output is:\n{raw}", strOutput || "<>" ); + fnShellRestore(); + joGlueResult = null; + } + return joGlueResult; +} + +function performBlsVerifyI( + details: log.TLogger, strDirection: string, nZeroBasedNodeIndex: number, + joResultFromNode: any, + jarrMessages: any[], nIdxCurrentMsgBlockStart: number, strFromChainName: string, + joPublicKey: discoveryTools.TBLSPublicKey +): boolean { + if( !joResultFromNode ) + return true; + const imaState: state.TIMAState = state.get(); + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const strLogPrefix = `${strDirection}/BLS/#${nZeroBasedNodeIndex}: `; + const nThreshold = discoverBlsThreshold( imaState.joSChainNetworkInfo ); + const nParticipants = discoverBlsParticipants( imaState.joSChainNetworkInfo ); + if( !checkBlsThresholdAndBlsParticipants( nThreshold, nParticipants, "BLS verify-I", details ) ) + return false; + const strActionDir = allocBlsTmpActionDir(); + const fnShellRestore = function(): void { shell.rm( "-rf", strActionDir ); }; + let strOutput = ""; + try { + details.trace( "{p}BLS node #{} - first message nonce is {}", + strLogPrefix, nZeroBasedNodeIndex, nIdxCurrentMsgBlockStart ); + details.trace( "{p}BLS node #{} - first source chain name is {}", + strLogPrefix, nZeroBasedNodeIndex, strFromChainName ); + details.trace( "{p}BLS node #{} - messages array {}", + strLogPrefix, nZeroBasedNodeIndex, jarrMessages ); + const strMessageHash = owaspUtils.removeStarting0x( + keccak256Message( jarrMessages, nIdxCurrentMsgBlockStart, strFromChainName ) ); + details.trace( "{p}BLS node #{} - hashed verify message is {}", + strLogPrefix, nZeroBasedNodeIndex, strMessageHash ); + const joMsg = { message: strMessageHash }; + details.debug( + "{p}BLS node #{} - composed {} composed from {} using glue {} and public key {}", + strLogPrefix, nZeroBasedNodeIndex, joMsg, jarrMessages, joResultFromNode, joPublicKey ); + const strSignResultFileName = strActionDir + "/sign-result" + nZeroBasedNodeIndex + ".json"; + imaUtils.jsonFileSave( strSignResultFileName, joResultFromNode ); + imaUtils.jsonFileSave( strActionDir + "/hash.json", joMsg ); + imaUtils.jsonFileSave( + strActionDir + "/BLS_keys" + nZeroBasedNodeIndex + ".json", joPublicKey ); + const strVerifyCommand = + imaState.strPathBlsVerify + + " --t " + nThreshold + + " --n " + nParticipants + + " --j " + nZeroBasedNodeIndex + + " --input " + strSignResultFileName; + details.trace( "{p}Will execute node #{} BLS verify command: {}", strLogPrefix, + nZeroBasedNodeIndex, strVerifyCommand ); + strOutput = childProcessModule.execSync( strVerifyCommand, { cwd: strActionDir } ) + .toString( "utf8" ); + details.trace( "{p}BLS node #{} verify output is:\n{raw}", strLogPrefix, + nZeroBasedNodeIndex, strOutput || "<>" ); + details.success( "{p}BLS node #{} verify success", strLogPrefix, nZeroBasedNodeIndex ); + fnShellRestore(); + return true; + } catch ( err ) { + details.critical( "{p}BLS node #{} verify error:, error description is: {err}, " + + "stack is: \n{stack}", strLogPrefix, nZeroBasedNodeIndex, err, err ); + details.critical( "{p}BLS node #{} verify output is:\n{raw}", + strLogPrefix, nZeroBasedNodeIndex, strOutput || "<>" ); + fnShellRestore(); + } + return false; +} + +function performBlsVerifyIU256( + details: log.TLogger, + nZeroBasedNodeIndex: number, joResultFromNode: any, u256: any, + joPublicKey: discoveryTools.TBLSPublicKey +): boolean { + if( !joResultFromNode ) + return true; + const imaState: state.TIMAState = state.get(); + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const strLogPrefix = `BLS/#${nZeroBasedNodeIndex}: `; + const nThreshold = discoverBlsThreshold( imaState.joSChainNetworkInfo ); + const nParticipants = discoverBlsParticipants( imaState.joSChainNetworkInfo ); + if( !checkBlsThresholdAndBlsParticipants( + nThreshold, nParticipants, "BLS verify-I-U256", details ) ) + return false; + const strActionDir = allocBlsTmpActionDir(); + const fnShellRestore = function(): void { shell.rm( "-rf", strActionDir ); }; + let strOutput = ""; + try { + const joMsg = { message: keccak256U256( u256, true ) }; + details.debug( "{p}BLS u256 node #{} verify message {} composed from {} using glue {} " + + "and public key {}", strLogPrefix, nZeroBasedNodeIndex, joMsg, u256, + joResultFromNode, joPublicKey ); + const strSignResultFileName = strActionDir + "/sign-result" + nZeroBasedNodeIndex + ".json"; + imaUtils.jsonFileSave( strSignResultFileName, joResultFromNode ); + imaUtils.jsonFileSave( strActionDir + "/hash.json", joMsg ); + imaUtils.jsonFileSave( + strActionDir + "/BLS_keys" + nZeroBasedNodeIndex + ".json", joPublicKey ); + const strVerifyCommand = + imaState.strPathBlsVerify + + " --t " + nThreshold + + " --n " + nParticipants + + " --j " + nZeroBasedNodeIndex + + " --input " + strSignResultFileName; + details.trace( "{p}Will execute node #{} BLS u256 verify command: {}", + strLogPrefix, nZeroBasedNodeIndex, strVerifyCommand ); + strOutput = childProcessModule.execSync( strVerifyCommand, { cwd: strActionDir } ) + .toString( "utf8" ); + details.trace( "{p}BLS u256 node #{} verify output is:\n{raw}", strLogPrefix, + nZeroBasedNodeIndex, strOutput || "<>" ); + details.success( "{p}BLS u256 node #{} verify success", strLogPrefix, nZeroBasedNodeIndex ); + fnShellRestore(); + return true; + } catch ( err ) { + details.error( "{p}BLS u256 node #{} verify error, error description is: {err}, " + + "stack is: \n{stack}", strLogPrefix, nZeroBasedNodeIndex, err, err ); + details.error( "{p}BLS u256 node #{} verify output is:\n{raw}", + strLogPrefix, nZeroBasedNodeIndex, strOutput || "<>" ); + fnShellRestore(); + } + return false; +} + +function performBlsVerify( + details: log.TLogger, strDirection: string, joGlueResult: any, + jarrMessages: any[], nIdxCurrentMsgBlockStart: number, strFromChainName: string, + joCommonPublicKey: discoveryTools.TBLSCommonPublicKey +): boolean { + if( !joGlueResult ) + return true; + const imaState: state.TIMAState = state.get(); + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const nThreshold = discoverBlsThreshold( imaState.joSChainNetworkInfo ); + const nParticipants = discoverBlsParticipants( imaState.joSChainNetworkInfo ); + if( !checkBlsThresholdAndBlsParticipants( nThreshold, nParticipants, "BLS verify", details ) ) + return false; + const strActionDir = allocBlsTmpActionDir(); + const fnShellRestore = function(): void { shell.rm( "-rf", strActionDir ); }; + let strOutput = ""; + const strLogPrefix = `${strDirection}/BLS/Summary: "`; + try { + details.trace( "{p}BLS/summary verify message - first message nonce is {}", + strLogPrefix, nIdxCurrentMsgBlockStart ); + details.trace( "{p}BLS/summary verify message - first source chain name is {}", + strLogPrefix, strFromChainName ); + details.trace( "{p}BLS/summary verify message - messages array {}", + strLogPrefix, jarrMessages ); + const strMessageHash = owaspUtils.removeStarting0x( + keccak256Message( jarrMessages, nIdxCurrentMsgBlockStart, strFromChainName ) ); + details.trace( "{p}BLS/summary verify message - hashed verify message is {}", + strLogPrefix, strMessageHash ); + const joMsg = { message: strMessageHash }; + details.debug( + "{p}BLS/summary verify message - composed JSON {} from messages array {}" + + " using glue {} and common public key {}", + strLogPrefix, joMsg, jarrMessages, joGlueResult, joCommonPublicKey ); + imaUtils.jsonFileSave( strActionDir + "/glue-result.json", joGlueResult ); + imaUtils.jsonFileSave( strActionDir + "/hash.json", joMsg ); + const joCommonPublicKeyToSave: discoveryTools.TBLSCommonPublicKey = { + commonBLSPublicKey0: joCommonPublicKey.commonBLSPublicKey0, + commonBLSPublicKey1: joCommonPublicKey.commonBLSPublicKey1, + commonBLSPublicKey2: joCommonPublicKey.commonBLSPublicKey2, + commonBLSPublicKey3: joCommonPublicKey.commonBLSPublicKey3 + }; + imaUtils.jsonFileSave( strActionDir + "/common_public_key.json", joCommonPublicKeyToSave ); + details.trace( "{p}BLS common public key for verification is:\n{}", + strLogPrefix, joCommonPublicKey ); + const strVerifyCommand = + imaState.strPathBlsVerify + + " --t " + nThreshold + + " --n " + nParticipants + + " --input " + "./glue-result.json"; + details.trace( "{p}Will execute BLS/summary verify command: {}", + strLogPrefix, strVerifyCommand ); + strOutput = childProcessModule.execSync( strVerifyCommand, { cwd: strActionDir } ) + .toString( "utf8" ); + details.trace( "{p}BLS/summary verify output is:\n{raw}", strLogPrefix, + strOutput || "<>" ); + details.success( "{p}BLS/summary verify success", strLogPrefix ); + fnShellRestore(); + return true; + } catch ( err ) { + details.error( "{p}BLS/summary verify error description is: {err}, stack is:\n{stack}", + strLogPrefix, err, err ); + details.error( "BLS/summary verify output is:\n{raw}", strOutput || "<>" ); + fnShellRestore(); + } + return false; +} + +function performBlsVerifyU256( + details: log.TLogger, joGlueResult: any, u256: any, + joCommonPublicKey: discoveryTools.TBLSCommonPublicKey +): boolean { + if( !joGlueResult ) + return true; + const imaState: state.TIMAState = state.get(); + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const nThreshold = discoverBlsThreshold( imaState.joSChainNetworkInfo ); + const nParticipants = discoverBlsParticipants( imaState.joSChainNetworkInfo ); + if( !checkBlsThresholdAndBlsParticipants( + nThreshold, nParticipants, "BLS verify-U256", details ) ) + return false; + const strActionDir = allocBlsTmpActionDir(); + const fnShellRestore = function(): void { shell.rm( "-rf", strActionDir ); }; + let strOutput = ""; + const strLogPrefix = "BLS u256/Summary: "; + try { + const joMsg = { message: keccak256U256( u256, true ) }; + details.debug( + "{p}BLS u256/summary verify message {} composed from {} using glue {}" + + " and common public key {}", + strLogPrefix, joMsg, u256, joGlueResult, joCommonPublicKey ); + imaUtils.jsonFileSave( strActionDir + "/glue-result.json", joGlueResult ); + imaUtils.jsonFileSave( strActionDir + "/hash.json", joMsg ); + const joCommonPublicKeyToSave: discoveryTools.TBLSCommonPublicKey = { + commonBLSPublicKey0: joCommonPublicKey.commonBLSPublicKey0, + commonBLSPublicKey1: joCommonPublicKey.commonBLSPublicKey1, + commonBLSPublicKey2: joCommonPublicKey.commonBLSPublicKey2, + commonBLSPublicKey3: joCommonPublicKey.commonBLSPublicKey3 + }; + imaUtils.jsonFileSave( strActionDir + "/common_public_key.json", joCommonPublicKeyToSave ); + details.trace( "{p}BLS u256 common public key for verification is:\n{}", + strLogPrefix, joCommonPublicKey ); + const strVerifyCommand = + imaState.strPathBlsVerify + + " --t " + nThreshold + + " --n " + nParticipants + + " --input " + "./glue-result.json"; + details.trace( "{p}Will execute BLS u256/summary verify command: {}", + strLogPrefix, strVerifyCommand ); + strOutput = childProcessModule.execSync( strVerifyCommand, { cwd: strActionDir } ) + .toString( "utf8" ); + details.trace( "{p}BLS u256/summary verify output is:\n{raw}", strLogPrefix, + strOutput || "<>" ); + details.success( "{p}BLS u256/summary verify success", strLogPrefix ); + fnShellRestore(); + return true; + } catch ( err ) { + details.error( "{p}BLS u256/summary error description is: {err}, stack is: \n{stack}", + strLogPrefix, err, err ); + details.error( "{p}BLS u256/summary verify output is:\n{raw}", strLogPrefix, + strOutput || "<>" ); + fnShellRestore(); + } + return false; +} + +async function checkCorrectnessOfMessagesToSign( + details: log.TLogger, strLogPrefix: string, strDirection: string, + jarrMessages: any[], nIdxCurrentMsgBlockStart: number, + joExtraSignOpts?: loop.TExtraSignOpts | null +): Promise < void > { + const imaState: state.TIMAState = state.get(); + let joMessageProxy: owaspUtils.ethersMod.ethers.Contract | null = null; + let joAccount: state.TAccount | null = null; + let joChainName: string | null = null; + if( strDirection == "M2S" ) { + joMessageProxy = imaState.joMessageProxyMainNet; + joAccount = imaState.chainProperties.mn.joAccount; + joChainName = imaState.chainProperties.sc.strChainName; + } else if( strDirection == "S2M" ) { + joMessageProxy = imaState.joMessageProxySChain; + joAccount = imaState.chainProperties.sc.joAccount; + joChainName = imaState.chainProperties.mn.strChainName; + } else if( strDirection == "S2S" ) { + joAccount = imaState.chainProperties.sc.joAccount; + if( ( !joExtraSignOpts?.chainNameDst ) ) + throw new Error( "Missing destination chain name for BLS signing" ); + joChainName = joExtraSignOpts.chainNameDst; + const ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider | null = + ( joExtraSignOpts && "ethersProviderSrc" in joExtraSignOpts && + joExtraSignOpts.ethersProviderSrc ) + ? joExtraSignOpts.ethersProviderSrc + : null; + if( !ethersProvider ) { + throw new Error( "CRITICAL ERROR: No provider specified in extra signing options " + + `for checking messages of direction ${strDirection}` ); + } + joMessageProxy = new owaspUtils.ethersMod.ethers.Contract( + imaState.chainProperties.sc.joAbiIMA.message_proxy_chain_address, + imaState.chainProperties.sc.joAbiIMA.message_proxy_chain_abi, + ethersProvider ); + } else { + throw new Error( "CRITICAL ERROR: Failed checkCorrectnessOfMessagesToSign() with " + + `unknown direction ${strDirection}` ); + } + + const strCallerAccountAddress = joAccount.address(); + details.debug( + "{p}{bright} message correctness validation through call to {sunny} method of " + + "MessageProxy contract with address {}, caller account address is {}, message(s) " + + "count is {}, message(s) to process are {}, first real message index is {}, messages " + + "will be sent to chain name {}", + strLogPrefix, strDirection, "verifyOutgoingMessageData", + joMessageProxy ? joMessageProxy.address : "", + strCallerAccountAddress, jarrMessages.length, jarrMessages, + nIdxCurrentMsgBlockStart, joChainName ); + let cntBadMessages = 0; let i = 0; + const cnt = jarrMessages.length; + if( strDirection == "S2M" || strDirection == "S2S" ) { + for( i = 0; i < cnt; ++i ) { + const joMessage = jarrMessages[i]; + const idxMessage = nIdxCurrentMsgBlockStart + i; + try { + details.trace( + "{p}{bright} Will validate message {} of {}, real message index is {}, " + + "source contract is {}, destination contract is {}, message data is {}", + strLogPrefix, strDirection, i, cnt, idxMessage, joMessage.sender, + joMessage.destinationContract, joMessage.data ); + const outgoingMessageData: any = { + dstChainHash: owaspUtils.ethersMod.ethers.utils.id( joChainName ), + msgCounter: idxMessage, + srcContract: joMessage.sender, + dstContract: joMessage.destinationContract, + data: joMessage.data + }; + if( !joMessageProxy ) + throw new Error( "No message proxy available" ); + const isValidMessage = await joMessageProxy.callStatic.verifyOutgoingMessageData( + outgoingMessageData, { from: strCallerAccountAddress } ); + details.trace( + "{p}{bright} Got verification call result {}, real message index is: {}, " + + "saved msgCounter is: {}", strLogPrefix, strDirection, + isValidMessage, +idxMessage, outgoingMessageData.msgCounter ); + if( !isValidMessage ) { + throw new Error( "Bad message detected, " + + `message is: ${JSON.stringify( joMessage )}` ); + } + } catch ( err ) { + ++cntBadMessages; + details.critical( + "{p}{bright} Correctness validation failed for message {} sent to {}, " + + "message is: {}, error information: {err}, stack is:\n{stack}", + strLogPrefix, strDirection, idxMessage, joChainName, joMessage, + err, err ); + } + } + } + // TODO: M2S - check events + if( cntBadMessages > 0 ) { + details.critical( "{p}Correctness validation failed for {} of {} message(s)", + strLogPrefix, cntBadMessages, cnt ); + } else + details.success( "{p}Correctness validation passed for {} message(s)", strLogPrefix, cnt ); +} + +async function prepareSignMessagesImpl( + optsSignOperation: TSignOperationOptions ): Promise < boolean > { + optsSignOperation.fn = optsSignOperation.fn || + // eslint-disable-next-line n/handle-callback-err + async function( + err: Error | string | null, + jarrMessages: any[], + joGlueResult: any | null + ): Promise < void > {}; + optsSignOperation.sequenceId = + owaspUtils.removeStarting0x( + owaspUtils.ethersMod.ethers.utils.id( log.generateTimestampString( null, false ) ) + ); + optsSignOperation.jarrNodes = + ( optsSignOperation.imaState.bSignMessages && + "joSChainNetworkInfo" in optsSignOperation.imaState && + optsSignOperation.imaState.joSChainNetworkInfo && + typeof optsSignOperation.imaState.joSChainNetworkInfo === "object" && + "network" in optsSignOperation.imaState.joSChainNetworkInfo && + typeof optsSignOperation.imaState.joSChainNetworkInfo.network === "object" + ) + ? optsSignOperation.imaState.joSChainNetworkInfo.network + : []; + optsSignOperation.details.trace( + "{p} Invoking {bright} signing messages procedure, message signing is {oo}", + optsSignOperation.strLogPrefix, optsSignOperation.strDirection, + optsSignOperation.imaState.bSignMessages ); + if( !( optsSignOperation.imaState.bSignMessages && + optsSignOperation.imaState.strPathBlsGlue.length > 0 && + optsSignOperation.imaState.joSChainNetworkInfo + ) ) { + optsSignOperation.bHaveResultReportCalled = true; + optsSignOperation.details.debug( + "{p}BLS message signing is turned off, first real message index is: {}, have {} " + + "message(s) to process {}", optsSignOperation.strLogPrefix, + optsSignOperation.nIdxCurrentMsgBlockStart, optsSignOperation.jarrMessages.length, + optsSignOperation.jarrMessages ); + optsSignOperation.details.exposeDetailsTo( + log.globalStream(), optsSignOperation.strGatheredDetailsName, false ); + optsSignOperation.details.close(); + await checkCorrectnessOfMessagesToSign( + optsSignOperation.details, optsSignOperation.strLogPrefix, + optsSignOperation.strDirection, + optsSignOperation.jarrMessages, + optsSignOperation.nIdxCurrentMsgBlockStart, + optsSignOperation.joExtraSignOpts ); + await optsSignOperation.fn( null, optsSignOperation.jarrMessages ); + return true; + } + await checkCorrectnessOfMessagesToSign( + optsSignOperation.details, optsSignOperation.strLogPrefix, + optsSignOperation.strDirection, + optsSignOperation.jarrMessages, optsSignOperation.nIdxCurrentMsgBlockStart, + optsSignOperation.joExtraSignOpts + ); + optsSignOperation.details.trace( "{p}Will sign {} message(s), sequence ID is {}...", + optsSignOperation.strLogPrefix, optsSignOperation.jarrMessages.length, + optsSignOperation.sequenceId ); + optsSignOperation.details.trace( "{p}Will query to sign {} skaled node(s)...", + optsSignOperation.strLogPrefix, optsSignOperation.jarrNodes.length ); + optsSignOperation.nThreshold = + discoverBlsThreshold( optsSignOperation.imaState.joSChainNetworkInfo ); + optsSignOperation.nParticipants = + discoverBlsParticipants( optsSignOperation.imaState.joSChainNetworkInfo ); + optsSignOperation.details.trace( "{p}Discovered BLS threshold is {}.", + optsSignOperation.strLogPrefix, optsSignOperation.nThreshold ); + optsSignOperation.details.trace( "{p}Discovered number of BLS participants is {}.", + optsSignOperation.strLogPrefix, optsSignOperation.nParticipants ); + if( !checkBlsThresholdAndBlsParticipants( + optsSignOperation.nThreshold, + optsSignOperation.nParticipants, + "prepare sign messages " + optsSignOperation.strDirection, + optsSignOperation.details ) ) { + optsSignOperation.bHaveResultReportCalled = true; + optsSignOperation.details.exposeDetailsTo( + log.globalStream(), optsSignOperation.strGatheredDetailsName, false ); + optsSignOperation.details.close(); + await optsSignOperation.fn( + "signature error(1), S-Chain information " + + "was not discovered properly and BLS threshold/participants are unknown", + optsSignOperation.jarrMessages, null ); + return false; + } + optsSignOperation.nCountOfBlsPartsToCollect = optsSignOperation.nThreshold; + optsSignOperation.details.trace( "{p}Will BLS-collect {} from {} nodes, sequence ID is {}", + optsSignOperation.strLogPrefix, optsSignOperation.nCountOfBlsPartsToCollect, + optsSignOperation.jarrNodes.length, optsSignOperation.sequenceId ); + return true; +} + +async function gatherSigningCheckFinish( + optsSignOperation: TSignOperationOptions ): Promise < boolean > { + const cntSuccess = optsSignOperation.arrSignResults.length; + if( optsSignOperation.joGatheringTracker.nCountReceivedPrevious != + optsSignOperation.joGatheringTracker.nCountReceived ) { + optsSignOperation.details.debug( + "{bright}/#{} BLS signature gathering progress updated, now have {} BLS " + + "parts of needed {} arrived, have {} success(es) and {} error(s)", + optsSignOperation.strDirection, optsSignOperation.nTransferLoopCounter, + optsSignOperation.joGatheringTracker.nCountReceived, + optsSignOperation.nCountOfBlsPartsToCollect, cntSuccess, + optsSignOperation.joGatheringTracker.nCountErrors ); + optsSignOperation.joGatheringTracker.nCountReceivedPrevious = + owaspUtils.toInteger( optsSignOperation.joGatheringTracker.nCountReceived ); + } + if( cntSuccess < optsSignOperation.nCountOfBlsPartsToCollect ) + return false; + optsSignOperation.strLogPrefixB = `${optsSignOperation.strDirection} /# ` + + `${optsSignOperation.nTransferLoopCounter}/BLS/Summary: `; + let strError: string | null = null; let strSuccessfulResultDescription: string | null = null; + const joGlueResult = performBlsGlue( optsSignOperation.details, + optsSignOperation.strDirection, optsSignOperation.jarrMessages, + optsSignOperation.nIdxCurrentMsgBlockStart, optsSignOperation.strFromChainName, + optsSignOperation.arrSignResults ); + if( joGlueResult ) { + optsSignOperation.details.success( "{p}Got BLS glue result: {}", + optsSignOperation.strLogPrefixB, joGlueResult ); + if( optsSignOperation.imaState.strPathBlsVerify.length > 0 ) { + if( !optsSignOperation.imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const joCommonPublicKey = discoverCommonPublicKey( + optsSignOperation.details, optsSignOperation.imaState.joSChainNetworkInfo, false ); + if( !joCommonPublicKey ) { + strError = "No BLS common public key"; + optsSignOperation.details.error( "{p}{err}", + optsSignOperation.strLogPrefixB, strError ); + } else if( performBlsVerify( + optsSignOperation.details, optsSignOperation.strDirection, + joGlueResult, optsSignOperation.jarrMessages, + optsSignOperation.nIdxCurrentMsgBlockStart, + optsSignOperation.strFromChainName, joCommonPublicKey + ) ) { + strSuccessfulResultDescription = "Got successful summary BLS verification result"; + optsSignOperation.details.success( "{p}{bright}", + optsSignOperation.strLogPrefixB, strSuccessfulResultDescription ); + } else { + strError = "BLS verification failed"; + optsSignOperation.details.error( "{p}{err}", + optsSignOperation.strLogPrefixB, strError ); + } + } + } else { + strError = "BLS glue failed, no glue result arrived"; + optsSignOperation.details.error( + "{p}Problem(1) in BLS sign result handler: {err}", + optsSignOperation.strLogPrefixB, strError ); + } + optsSignOperation.details.trace( + "Will call signed-hash answer-sending callback {}, messages is(are) {}, " + + "glue result is {}", strError ? log.fmtError( " with error {}", strError ) : "", + optsSignOperation.jarrMessages, joGlueResult ); + optsSignOperation.fn( + strError, optsSignOperation.jarrMessages, joGlueResult ) + .catch( function( err: Error | string ): void { + optsSignOperation.details.critical( + "Problem(2) in BLS sign result handler: {err}", err ); + optsSignOperation.errGathering = "Problem(2) in BLS sign " + + `result handler: ${owaspUtils.extractErrorMessage( err )}`; + } ); + optsSignOperation.bHaveResultReportCalled = true; + return true; +} + +async function gatherSigningCheckOverflow( + optsSignOperation: TSignOperationOptions ): Promise < boolean > { + if( optsSignOperation.joGatheringTracker.nCountReceived < optsSignOperation.jarrNodes.length ) + return false; + optsSignOperation.fn( + `signature error(2), got ${optsSignOperation.joGatheringTracker.nCountErrors}` + + ` errors(s) for ${optsSignOperation.jarrNodes.length} node(s)`, + optsSignOperation.jarrMessages, null + ).catch( function( err: Error | string ): void { + const cntSuccess = optsSignOperation.arrSignResults.length; + optsSignOperation.details.error( + "Problem(3) in BLS sign result handler, not enough successful BLS signature " + + "parts({}) when all attempts done, error details: {err}", cntSuccess, err ); + optsSignOperation.errGathering = "Problem(3) in BLS sign result handler, not enough " + + `successful BLS signature parts(${cntSuccess}) when all attempts done, ` + + `error details: ${owaspUtils.extractErrorMessage( err )}`; + } ); + optsSignOperation.bHaveResultReportCalled = true; + return true; +} + +async function gatherSigningStartImpl( + optsSignOperation: TSignOperationOptions ): Promise < void > { + optsSignOperation.details.debug( "{p}Waiting for BLS glue result...", + optsSignOperation.strLogPrefix ); + optsSignOperation.errGathering = null; + for( let idxStep = 0; idxStep < optsSignOperation.joGatheringTracker.nWaitIntervalMaxSteps; + ++idxStep ) { + await threadInfo.sleep( + optsSignOperation.joGatheringTracker.nWaitIntervalStepMilliseconds ); + if( await gatherSigningCheckFinish( optsSignOperation ) ) + return; + if( await gatherSigningCheckOverflow( optsSignOperation ) ) + return; + } + // timeout + optsSignOperation.fn( + `signature error(3), got ${optsSignOperation.joGatheringTracker.nCountErrors}` + + ` errors(s) for ${optsSignOperation.jarrNodes.length} node(s)`, + optsSignOperation.jarrMessages, null + ).catch( function( err: Error | string ): void { + const cntSuccess = optsSignOperation.arrSignResults.length; + optsSignOperation.details.critical( + "Problem(4) in BLS sign result handler, not enough successful BLS signature " + + "parts({}) and timeout reached, error details: {err}", cntSuccess, err ); + optsSignOperation.errGathering = "Problem(4) in BLS sign result handler, not enough " + + `successful BLS signature parts(${cntSuccess}) and timeout reached, ` + + `error details: ${owaspUtils.extractErrorMessage( err )}`; + } ); + optsSignOperation.bHaveResultReportCalled = true; +} + +async function gatherSigningFinishImpl( + optsSignOperation: TSignOperationOptions ): Promise < void > { + optsSignOperation.details.trace( "{p}Will await for message BLS verification and sending...", + optsSignOperation.strLogPrefix ); + if( optsSignOperation.errGathering ) { + optsSignOperation.details.error( "Failed BLS sign result awaiting(1): {err}", + optsSignOperation.errGathering.toString() ); + if( !optsSignOperation.bHaveResultReportCalled ) { + optsSignOperation.bHaveResultReportCalled = true; + await optsSignOperation.fn( + `Failed to gather BLS signatures in ${optsSignOperation.jarrNodes.length} ` + + "node(s), tracker data is: " + + `${JSON.stringify( optsSignOperation.joGatheringTracker )} , ` + + `error is: ${optsSignOperation.errGathering.toString()}`, + optsSignOperation.jarrMessages, null + ).catch( function( err: Error | string ): void { + const cntSuccess = optsSignOperation.arrSignResults.length; + optsSignOperation.details.error( + "Problem(5) in BLS sign result handler, not enough successful BLS " + + "signature parts({}) and timeout reached, error details: {err}", + cntSuccess, err ); + optsSignOperation.details.exposeDetailsTo( + log.globalStream(), optsSignOperation.strGatheredDetailsName, false ); + optsSignOperation.details.close(); + optsSignOperation.details = log.globalStream(); + } ); + } + return; + } + if( !optsSignOperation.bHaveResultReportCalled ) { + optsSignOperation.details.error( "Failed BLS sign result awaiting(2): {err}", + "No reports were arrived" ); + optsSignOperation.bHaveResultReportCalled = true; + await optsSignOperation.fn( + `Failed to gather BLS signatures in ${optsSignOperation.jarrNodes.length} node(s), ` + + `tracker data is: ${JSON.stringify( optsSignOperation.joGatheringTracker )}`, + optsSignOperation.jarrMessages, null + ).catch( function( err: Error | string ): void { + const cntSuccess = optsSignOperation.arrSignResults.length; + optsSignOperation.details.error( + "Problem(6) in BLS sign result handler, not enough successful BLS signature " + + "parts({}) and timeout reached, error details: {err}", cntSuccess, err ); + optsSignOperation.details.exposeDetailsTo( + log.globalStream(), optsSignOperation.strGatheredDetailsName, false ); + optsSignOperation.details.close(); + optsSignOperation.details = log.globalStream(); + } ); + } +} + +async function doSignConfigureChainAccessParams( + optsSignOperation: TSignOperationOptions ): Promise < void > { + optsSignOperation.targetChainName = ""; + optsSignOperation.fromChainName = ""; + optsSignOperation.targetChainID = -4; + optsSignOperation.fromChainID = -4; + if( optsSignOperation.strDirection == "M2S" ) { + optsSignOperation.targetChainName = + ( optsSignOperation.imaState.chainProperties.sc.strChainName + ? optsSignOperation.imaState.chainProperties.sc.strChainName + : "" ); + optsSignOperation.fromChainName = + ( optsSignOperation.imaState.chainProperties.mn.strChainName + ? optsSignOperation.imaState.chainProperties.mn.strChainName + : "" ); + optsSignOperation.targetChainID = optsSignOperation.imaState.chainProperties.sc.chainId; + optsSignOperation.fromChainID = optsSignOperation.imaState.chainProperties.mn.chainId; + } else if( optsSignOperation.strDirection == "S2M" ) { + optsSignOperation.targetChainName = + ( optsSignOperation.imaState.chainProperties.mn.strChainName + ? optsSignOperation.imaState.chainProperties.mn.strChainName + : "" ); + optsSignOperation.fromChainName = + ( optsSignOperation.imaState.chainProperties.sc.strChainName + ? optsSignOperation.imaState.chainProperties.sc.strChainName + : "" ); + optsSignOperation.targetChainID = optsSignOperation.imaState.chainProperties.mn.chainId; + optsSignOperation.fromChainID = optsSignOperation.imaState.chainProperties.sc.chainId; + } else if( optsSignOperation.strDirection == "S2S" ) { + if( !optsSignOperation.joExtraSignOpts ) + throw new Error( "No S2S signing options provided" ); + optsSignOperation.targetChainName = optsSignOperation.joExtraSignOpts.chainNameDst; + optsSignOperation.fromChainName = optsSignOperation.joExtraSignOpts.chainNameSrc; + optsSignOperation.targetChainID = optsSignOperation.joExtraSignOpts.chainIdDst; + optsSignOperation.fromChainID = optsSignOperation.joExtraSignOpts.chainIdSrc; + } else { + throw new Error( "CRITICAL ERROR: Failed doSignMessagesImpl() with " + + `unknown direction ${optsSignOperation.strDirection}` ); + } +} + +async function doSignProcessHandleCall( + optsSignOperation: TSignOperationOptions, joParams: any, joCall: rpcCall.TRPCCall, + joIn: any, joOut: any, i: number ): Promise < void > { + const imaState: state.TIMAState = state.get(); + const isThisNode = ( i == imaState.nNodeNumber ); + const joNode = optsSignOperation.jarrNodes[i]; + const strNodeURL = optsSignOperation.imaState.isCrossImaBlsMode + ? imaUtils.composeImaAgentNodeUrl( joNode, isThisNode ) + : imaUtils.composeSChainNodeUrl( joNode ); + const strNodeDescColorized = log.fmtDebug( "{url} ({}/{}, ID {}), sequence ID is {}", + strNodeURL, i, optsSignOperation.jarrNodes.length, joNode.nodeID, + optsSignOperation.sequenceId ); + ++optsSignOperation.joGatheringTracker.nCountReceived; + optsSignOperation.details.trace( + "{p}{} Got answer from {bright}(node #{} via {url} for transfer from chain {} " + + "to chain {} with params {}, answer is {}, sequence ID is {}", + optsSignOperation.strLogPrefix, log.generateTimestampString( null, true ), + "skale_imaVerifyAndSign", i, strNodeURL, optsSignOperation.fromChainName, + optsSignOperation.targetChainName, joParams, joOut, optsSignOperation.sequenceId ); + if( ( !joOut ) || typeof joOut !== "object" || ( !( "result" in joOut ) ) || + ( !joOut.result ) || typeof joOut.result !== "object" || + ( "error" in joOut && joOut.error ) ) { + ++optsSignOperation.joGatheringTracker.nCountErrors; + optsSignOperation.details.critical( + "{p}S-Chain node {} reported wallet error: {err}, sequence ID is ", + optsSignOperation.strLogPrefix, strNodeDescColorized, + owaspUtils.extractErrorMessage( joOut, "unknown wallet error(1)" ), + optsSignOperation.sequenceId ); + await joCall.disconnect(); + return; + } + optsSignOperation.details.debug( "{p}Node {} sign result: {}", + optsSignOperation.strLogPrefix, joNode.nodeID, joOut.result ? joOut.result : null ); + try { + if( joOut.result.signResult.signatureShare.length > 0 && + joOut.result.signResult.status === 0 + ) { + const nZeroBasedNodeIndex = joNode.imaInfo.thisNodeIndex - 1; + // partial BLS verification for one participant + let bNodeSignatureOKay = false; // initially assume signature is wrong + optsSignOperation.strLogPrefixA = + `${optsSignOperation.strDirection}/BLS/#${nZeroBasedNodeIndex}: `; + try { + const cntSuccess = optsSignOperation.arrSignResults.length; + if( cntSuccess > optsSignOperation.nCountOfBlsPartsToCollect ) { + ++optsSignOperation.joGatheringTracker.nCountSkipped; + optsSignOperation.details.notice( + "{p}Will ignore sign result for node {} because {}/{} threshold number " + + "of BLS signature parts already gathered", + optsSignOperation.strLogPrefixA, nZeroBasedNodeIndex, + optsSignOperation.nThreshold, optsSignOperation.nCountOfBlsPartsToCollect ); + await joCall.disconnect(); + return; + } + const arrTmp = joOut.result.signResult.signatureShare.split( ":" ); + const joResultFromNode: any = { + index: nZeroBasedNodeIndex.toString(), + signature: { + X: arrTmp[0], + Y: arrTmp[1] + } + }; + optsSignOperation.details.trace( "{p}Will verify sign result for node {}", + optsSignOperation.strLogPrefixA, nZeroBasedNodeIndex ); + if( !optsSignOperation.imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const joPublicKey = discoverPublicKeyByIndex( nZeroBasedNodeIndex, + optsSignOperation.imaState.joSChainNetworkInfo, optsSignOperation.details, + true ); + if( !joPublicKey ) + throw new Error( `No BLS public key for node ${nZeroBasedNodeIndex}` ); + if( performBlsVerifyI( + optsSignOperation.details, optsSignOperation.strDirection, + nZeroBasedNodeIndex, joResultFromNode, + optsSignOperation.jarrMessages, + optsSignOperation.nIdxCurrentMsgBlockStart, + optsSignOperation.strFromChainName, + joPublicKey + ) ) { + optsSignOperation.details.success( + "{p}Got successful BLS verification result for node {} with index {}", + optsSignOperation.strLogPrefixA, joNode.nodeID, nZeroBasedNodeIndex ); + bNodeSignatureOKay = true; // node verification passed + } else { + optsSignOperation.details.error( "{p} BLS verification failed", + optsSignOperation.strLogPrefixA ); + } + } catch ( err ) { + optsSignOperation.details.critical( + "{p}S-Chain node {} partial signature fail from with index {}" + + ", error is {err}, sequence ID is {}, stack is:\n{stack}", + optsSignOperation.strLogPrefixA, strNodeDescColorized, nZeroBasedNodeIndex, + err, optsSignOperation.sequenceId, err ); + } + if( bNodeSignatureOKay ) { + optsSignOperation.arrSignResults.push( { + index: nZeroBasedNodeIndex.toString(), + signature: splitSignatureShare( joOut.result.signResult.signatureShare ), + fromNode: joNode, // extra, not needed for bls_glue + signResult: joOut.result.signResult + } ); + } else + ++optsSignOperation.joGatheringTracker.nCountErrors; + } + } catch ( err ) { + ++optsSignOperation.joGatheringTracker.nCountErrors; + optsSignOperation.details.critical( + "{p}S-Chain node {} signature fail from node {}, error is {err}" + + ", sequence ID is {}, stack is:\n{stack}", + optsSignOperation.strLogPrefix, strNodeDescColorized, joNode.nodeID, + err, optsSignOperation.sequenceId, err ); + } + await joCall.disconnect(); +} + +async function doSignProcessOneImpl( + i: number, optsSignOperation: TSignOperationOptions ): Promise < void > { + const imaState: state.TIMAState = state.get(); + const isThisNode = ( i == imaState.nNodeNumber ); + const joNode = optsSignOperation.jarrNodes[i]; + const strNodeURL = optsSignOperation.imaState.isCrossImaBlsMode + ? imaUtils.composeImaAgentNodeUrl( joNode, isThisNode ) + : imaUtils.composeSChainNodeUrl( joNode ); + const strNodeDescColorized = log.fmtDebug( "{url} ({}/{}, ID {}), sequence ID is {}", + strNodeURL, i, optsSignOperation.jarrNodes.length, joNode.nodeID, + optsSignOperation.sequenceId ); + const rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + const joCall = + await rpcCall.create( strNodeURL, rpcCallOpts + ).catch( function( err: Error | string ): void { + ++optsSignOperation.joGatheringTracker.nCountReceived; + ++optsSignOperation.joGatheringTracker.nCountErrors; + optsSignOperation.details.error( + "{p}JSON RPC call(doSignProcessOneImpl) to S-Chain node {} failed, " + + "RPC call failed, error is: {err}, sequence ID is {}", + optsSignOperation.strLogPrefix, strNodeDescColorized, + err, optsSignOperation.sequenceId ); + if( joCall ) + joCall.disconnect().then( function(): void {} ).catch( function(): void {} ); + } ); + if( !joCall ) + return; + await doSignConfigureChainAccessParams( optsSignOperation ); + const joParams: THandleVerifyAndSignCallDataParams = { + direction: optsSignOperation.strDirection, + startMessageIdx: optsSignOperation.nIdxCurrentMsgBlockStart, + dstChainName: optsSignOperation.targetChainName, + srcChainName: optsSignOperation.fromChainName, + dstChainID: optsSignOperation.targetChainID.toString(), + srcChainID: optsSignOperation.fromChainID.toString(), + messages: optsSignOperation.jarrMessages, + qa: { + skaledNumber: owaspUtils.toInteger( i ), + sequenceId: optsSignOperation.sequenceId, + ts: log.generateTimestampString( null, false ) + } + }; + optsSignOperation.details.trace( + "{p}{} Will invoke {bright} to node #{} via {url} for transfer from chain {} " + + "to chain {} with params {}, sequence ID is {}", optsSignOperation.strLogPrefix, + log.generateTimestampString( null, true ), "skale_imaVerifyAndSign", i, strNodeURL, + optsSignOperation.fromChainName, optsSignOperation.targetChainName, + joParams, optsSignOperation.sequenceId ); + const joIn: any = { method: "skale_imaVerifyAndSign", params: joParams }; + const joOut = await joCall.call( joIn ); + await doSignProcessHandleCall( optsSignOperation, joParams, joCall, joIn, joOut, i ); +} + +async function doSignMessagesImpl( + nTransferLoopCounter: number, strDirection: string, + jarrMessages: any[], nIdxCurrentMsgBlockStart: number, strFromChainName: string, + joExtraSignOpts?: loop.TExtraSignOpts | null, fn?: IMA.TFunctionAfterSigningMessages +): Promise < void > { + const optsSignOperation: TSignOperationOptions = { + imaState: state.get(), + nTransferLoopCounter, + strDirection, + jarrMessages, + nIdxCurrentMsgBlockStart, + strFromChainName, + joExtraSignOpts, + // eslint-disable-next-line n/handle-callback-err + fn: fn ?? async function( + err: Error | string | null, jarrMessages: any[], joGlueResult: any | null + ): Promise < void > {}, + bHaveResultReportCalled: false, + strLogPrefix: "", + strLogPrefixA: "", + strLogPrefixB: "", + joGatheringTracker: { + nCountReceivedPrevious: 0, + nCountReceived: 0, + nCountErrors: 0, + nCountSkipped: 0, + nWaitIntervalStepMilliseconds: 500, + nWaitIntervalMaxSteps: 10 * 60 * 3 // 10 is 1 second + }, + arrSignResults: [], + details: log.globalStream(), + strGatheredDetailsName: "", + sequenceId: "", + jarrNodes: [], + nThreshold: 1, + nParticipants: 1, + nCountOfBlsPartsToCollect: 1, + errGathering: null, + targetChainName: "", + fromChainName: "", + targetChainID: -4, + fromChainID: -4 + }; + optsSignOperation.strLogPrefix = `${optsSignOperation.strDirection}/#` + + `${optsSignOperation.nTransferLoopCounter} Sign msgs via ` + + `${optsSignOperation.imaState.isCrossImaBlsMode ? "IMA agent" : "skaled"}: `; + optsSignOperation.joGatheringTracker = { + nCountReceivedPrevious: 0, + nCountReceived: 0, + nCountErrors: 0, + nCountSkipped: 0, + nWaitIntervalStepMilliseconds: 500, + nWaitIntervalMaxSteps: 10 * 60 * 3 // 10 is 1 second + }; + optsSignOperation.details = optsSignOperation.imaState.isDynamicLogInBlsSigner + ? log.globalStream() + : log.createMemoryStream(); + optsSignOperation.strGatheredDetailsName = optsSignOperation.strDirection + "-" + + "doSignMessagesImpl-#" + optsSignOperation.nTransferLoopCounter + + "-" + optsSignOperation.strFromChainName + "-msg#" + + optsSignOperation.nIdxCurrentMsgBlockStart; + try { + if( !( await prepareSignMessagesImpl( optsSignOperation ) ) ) + return; + for( let i = 0; i < optsSignOperation.jarrNodes.length; ++i ) { + const cntSuccess = optsSignOperation.arrSignResults.length; + if( cntSuccess >= optsSignOperation.nCountOfBlsPartsToCollect ) { + optsSignOperation.details.trace( + "{p}{} Stop invoking {bright} for transfer from chain {} at #{} because " + + "successfully gathered count is reached ", optsSignOperation.strLogPrefix, + log.generateTimestampString( null, true ), "skale_imaVerifyAndSign", + strFromChainName, i, cntSuccess ); + break; + } + doSignProcessOneImpl( i, optsSignOperation ) + .then( function(): void {} ).catch( function( err ): void { + log.error( + "Failed single BLS sign processing, reported error is: {err}", err ); + } ); + } + await gatherSigningStartImpl( optsSignOperation ); + await gatherSigningFinishImpl( optsSignOperation ); + } catch ( err ) { + if( optsSignOperation.details ) { + optsSignOperation.details.critical( "Failed BLS sign due to generic " + + "flow exception: {err}, stack is:\n{stack}", err, err ); + } + if( !optsSignOperation.bHaveResultReportCalled ) { + optsSignOperation.bHaveResultReportCalled = true; + await optsSignOperation.fn( "Failed BLS sign due to exception: " + + `${owaspUtils.extractErrorMessage( err )}`, optsSignOperation.jarrMessages, null + ).catch( function( err: Error | string ): void { + log.critical( "Failed BLS sign due to error-reporting callback exception: {err}", + err ); + if( optsSignOperation.details ) { + optsSignOperation.details.critical( + "Failed BLS sign due to error-reporting callback exception: {err}", + err ); + optsSignOperation.details.exposeDetailsTo( + log.globalStream(), optsSignOperation.strGatheredDetailsName, false ); + optsSignOperation.details.close(); + } + } ); + } + } + optsSignOperation.details.success( "{p} completed", optsSignOperation.strGatheredDetailsName ); + if( optsSignOperation.details ) { + optsSignOperation.details.exposeDetailsTo( + log.globalStream(), optsSignOperation.strGatheredDetailsName, true ); + optsSignOperation.details.close(); + } +} + +export async function doSignMessagesM2S( + nTransferLoopCounter: number, + jarrMessages: any[], nIdxCurrentMsgBlockStart: number, strFromChainName: string, + joExtraSignOpts?: loop.TExtraSignOpts | null, fn?: IMA.TFunctionAfterSigningMessages +): Promise < void > { + await doSignMessagesImpl( + nTransferLoopCounter, "M2S", + jarrMessages, nIdxCurrentMsgBlockStart, strFromChainName, + joExtraSignOpts, fn ); +} + +export async function doSignMessagesS2M( + nTransferLoopCounter: number, + jarrMessages: any[], nIdxCurrentMsgBlockStart: number, strFromChainName: string, + joExtraSignOpts?: loop.TExtraSignOpts | null, fn?: IMA.TFunctionAfterSigningMessages +): Promise < void > { + await doSignMessagesImpl( + nTransferLoopCounter, "S2M", + jarrMessages, nIdxCurrentMsgBlockStart, strFromChainName, + joExtraSignOpts, fn ); +} + +export async function doSignMessagesS2S( + nTransferLoopCounter: number, + jarrMessages: any[], nIdxCurrentMsgBlockStart: number, strFromChainName: string, + joExtraSignOpts?: loop.TExtraSignOpts | null, fn?: IMA.TFunctionAfterSigningMessages +): Promise < void > { + await doSignMessagesImpl( + nTransferLoopCounter, "S2S", + jarrMessages, nIdxCurrentMsgBlockStart, strFromChainName, + joExtraSignOpts, fn ); +} + +async function prepareSignU256( optsSignU256: TSignU256Options ): Promise < boolean > { + optsSignU256.details.debug( "{p}Will sign {} value...", + optsSignU256.strLogPrefix, optsSignU256.u256 ); + optsSignU256.details.trace( "{p}Will query to sign {} skaled node(s)...", + optsSignU256.strLogPrefix, optsSignU256.jarrNodes.length ); + if( !optsSignU256.imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + optsSignU256.nThreshold = discoverBlsThreshold( optsSignU256.imaState.joSChainNetworkInfo ); + optsSignU256.nParticipants = + discoverBlsParticipants( optsSignU256.imaState.joSChainNetworkInfo ); + optsSignU256.details.trace( "{p}Discovered BLS threshold is {}.", + optsSignU256.strLogPrefix, optsSignU256.nThreshold ); + optsSignU256.details.trace( "{p}Discovered number of BLS participants is {}.", + optsSignU256.strLogPrefix, optsSignU256.nParticipants ); + if( !checkBlsThresholdAndBlsParticipants( + optsSignU256.nThreshold, + optsSignU256.nParticipants, + "prepare sign-U256", + optsSignU256.details ) ) { + await optsSignU256.fn( + "signature error(1, u256), S-Chain information " + + "was not discovered properly and BLS threshold/participants are unknown", + optsSignU256.u256, null ); + return false; + } + optsSignU256.nCountOfBlsPartsToCollect = optsSignU256.nThreshold; + optsSignU256.details.trace( "{p}Will(optsSignU256.u256) collect {} from {} nodes", + optsSignU256.strLogPrefix, optsSignU256.nCountOfBlsPartsToCollect, + optsSignU256.jarrNodes.length ); + return true; +} + +async function doSignU256OneImplHandleCallResult( + i: number, optsSignU256: TSignU256Options, joCall: rpcCall.TRPCCall, joIn: any, joOut: any +): Promise < void > { + const imaState: state.TIMAState = state.get(); + const isThisNode = ( i == imaState.nNodeNumber ); + const joNode = optsSignU256.jarrNodes[i]; + const strNodeURL = optsSignU256.imaState.isCrossImaBlsMode + ? imaUtils.composeImaAgentNodeUrl( joNode, isThisNode ) + : imaUtils.composeSChainNodeUrl( joNode ); + const strNodeDescColorized = log.fmtDebug( "{url} ({}/{}, ID {})", + strNodeURL, i, optsSignU256.jarrNodes.length, joNode.nodeID ); + ++optsSignU256.joGatheringTracker.nCountReceived; + optsSignU256.details.trace( "{p}Did invoked {} for to sign value {}, answer is: {}", + optsSignU256.strLogPrefix, "skale_imaBSU256", optsSignU256.u256.toString(), joOut ); + if( ( !joOut ) || typeof joOut !== "object" || ( !( "result" in joOut ) ) || + ( "error" in joOut && joOut.error ) || + ( !joOut.result ) || typeof joOut.result !== "object" || + ( !( "signature" in joOut.result ) ) || joOut.result.signature != "object" + ) { + ++optsSignU256.joGatheringTracker.nCountErrors; + const strErrorMessage = + owaspUtils.extractErrorMessage( joOut, "unknown wallet error(2)" ); + optsSignU256.details.error( "{p}S-Chain node {} reported wallet error: {err}", + optsSignU256.strLogPrefix, strNodeDescColorized, strErrorMessage ); + await joCall.disconnect(); + return; + } + optsSignU256.details.trace( "{p}Node {} sign result: ", + optsSignU256.strLogPrefix, joNode.nodeID, joOut.result ? joOut.result : null ); + try { + if( joOut.result.signResult.signatureShare.length > 0 && + joOut.result.signResult.status === 0 + ) { + const nZeroBasedNodeIndex = joNode.imaInfo.thisNodeIndex - 1; + // partial BLS verification for one participant + let bNodeSignatureOKay = false; // initially assume signature is wrong + const strLogPrefixA = `BLS/#${nZeroBasedNodeIndex}: `; + try { + const cntSuccess = optsSignU256.arrSignResults.length; + if( cntSuccess > optsSignU256.nCountOfBlsPartsToCollect ) { + ++optsSignU256.joGatheringTracker.nCountSkipped; + optsSignU256.details.notice( + "{p}Will ignore sign result for node {} because {}/{} threshold " + + "number of BLS signature parts already gathered", strLogPrefixA, + nZeroBasedNodeIndex, optsSignU256.nThreshold, + optsSignU256.nCountOfBlsPartsToCollect ); + return; + } + const arrTmp = joOut.result.signResult.signatureShare.split( ":" ); + const joResultFromNode: any = { + index: nZeroBasedNodeIndex.toString(), + signature: { X: arrTmp[0], Y: arrTmp[1] } + }; + optsSignU256.details.trace( "{p}Will verify sign result for node {}", + strLogPrefixA, nZeroBasedNodeIndex ); + if( !optsSignU256.imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const joPublicKey = discoverPublicKeyByIndex( nZeroBasedNodeIndex, + optsSignU256.imaState.joSChainNetworkInfo, optsSignU256.details, + true ); + if( !joPublicKey ) + throw new Error( `No BLS public key for node ${nZeroBasedNodeIndex}` ); + if( performBlsVerifyIU256( + optsSignU256.details, nZeroBasedNodeIndex, joResultFromNode, + optsSignU256.u256, joPublicKey ) ) { + optsSignU256.details.success( + "{p}Got successful BLS verification result for node {} " + + "with index {}", strLogPrefixA, joNode.nodeID, + nZeroBasedNodeIndex ); + bNodeSignatureOKay = true; // node verification passed + } else { + optsSignU256.details.error( "{p} BLS u256 one node verify failed", + strLogPrefixA ); + } + } catch ( err ) { + optsSignU256.details.critical( + "{p}S-Chain node {} sign CRITICAL ERROR: partial signature fail from " + + "with index {}, error is {err}, stack is:\n{stack}", + strLogPrefixA, strNodeDescColorized, nZeroBasedNodeIndex, + err, err ); + } + if( bNodeSignatureOKay ) { + optsSignU256.arrSignResults.push( { + index: nZeroBasedNodeIndex.toString(), + signature: + splitSignatureShare( joOut.result.signResult.signatureShare ), + fromNode: joNode, // extra, not needed for bls_glue + signResult: joOut.result.signResult + } ); + } else + ++optsSignU256.joGatheringTracker.nCountErrors; + } + } catch ( err ) { + ++optsSignU256.joGatheringTracker.nCountErrors; + optsSignU256.details.critical( + "{p}S-Chain node {} signature fail from node {}, error is {err}, " + + "stack is:\n{stack}", optsSignU256.strLogPrefix, + strNodeDescColorized, joNode.nodeID, err, err ); + } + await joCall.disconnect(); +} + +async function doSignU256OneImpl( + i: number, optsSignU256: TSignU256Options ): Promise < boolean> { + const imaState: state.TIMAState = state.get(); + const isThisNode = ( i == imaState.nNodeNumber ); + const joNode = optsSignU256.jarrNodes[i]; + const strNodeURL = optsSignU256.imaState.isCrossImaBlsMode + ? imaUtils.composeImaAgentNodeUrl( joNode, isThisNode ) + : imaUtils.composeSChainNodeUrl( joNode ); + const strNodeDescColorized = log.fmtDebug( "{url} ({}/{}, ID {})", + strNodeURL, i, optsSignU256.jarrNodes.length, joNode.nodeID ); + const rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + let joCall: rpcCall.TRPCCall | null = null; + try { + joCall = await rpcCall.create( strNodeURL, rpcCallOpts ); + if( !joCall ) + throw new Error( `Failed to create JSON RPC call object to ${strNodeURL}` ); + optsSignU256.details.trace( "{p}Will invoke skale_imaBSU256 for to sign value {}", + optsSignU256.strLogPrefix, optsSignU256.u256.toString() ); + const joIn: any = { + method: "skale_imaBSU256", + params: { + valueToSign: optsSignU256.u256 // must be 0x string, came from outside 0x string + } + }; + const joOut = await joCall.call( joIn ); + await doSignU256OneImplHandleCallResult( i, optsSignU256, joCall, joIn, joOut ); + return true; + } catch ( err ) { + ++optsSignU256.joGatheringTracker.nCountReceived; + ++optsSignU256.joGatheringTracker.nCountErrors; + optsSignU256.details.error( + "{p}JSON RPC call(doSignU256OneImpl) to S-Chain node {} failed, RPC call was " + + "not created, error is: {err}", + optsSignU256.strLogPrefix, strNodeDescColorized, err ); + if( joCall ) + await joCall.disconnect(); + return true; + } +} + +async function gatherSigningCheckFinish256( + optsSignU256: TSignU256Options ): Promise < boolean > { + const cntSuccess = optsSignU256.arrSignResults.length; + if( optsSignU256.joGatheringTracker.nCountReceivedPrevious != + optsSignU256.joGatheringTracker.nCountReceived ) { + optsSignU256.details.debug( + "BLS u256 - BLS signature gathering progress updated, now have {} BLS parts " + + "of needed {} arrived, have {} success(es) and {} error(s)", + optsSignU256.joGatheringTracker.nCountReceived, + optsSignU256.nCountOfBlsPartsToCollect, cntSuccess, + optsSignU256.joGatheringTracker.nCountErrors ); + optsSignU256.joGatheringTracker.nCountReceivedPrevious = + owaspUtils.toInteger( optsSignU256.joGatheringTracker.nCountReceived ); + } + if( cntSuccess < optsSignU256.nCountOfBlsPartsToCollect ) + return false; + const strLogPrefixB = "BLS u256/Summary: "; + const strError = null; + const joGlueResult = performBlsGlueU256( + optsSignU256.details, optsSignU256.u256, optsSignU256.arrSignResults ); + if( joGlueResult ) { + optsSignU256.details.success( "{p}Got BLS glue u256 result: {}", + strLogPrefixB, joGlueResult ); + if( optsSignU256.imaState.strPathBlsVerify.length > 0 ) { + if( !optsSignU256.imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const joCommonPublicKey = discoverCommonPublicKey( + optsSignU256.details, optsSignU256.imaState.joSChainNetworkInfo, false ); + if( !joCommonPublicKey ) { + if( !optsSignU256.imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const strError = "No BLS common public key"; + optsSignU256.details.error( "{p}{}", strLogPrefixB, strError ); + } else if( performBlsVerifyU256( optsSignU256.details, joGlueResult, + optsSignU256.u256, joCommonPublicKey ) ) { + const strSuccessfulResultDescription = + "Got successful summary BLS u256 verification result"; + optsSignU256.details.success( "{p}{}", strLogPrefixB, + strSuccessfulResultDescription ); + } else { + const strError = "BLS verification failed"; + optsSignU256.details.error( "{p}BLS verification failure:{}", + strLogPrefixB, strError ); + } + } + } else { + const strError = "BLS u256 glue failed, no glue result arrived"; + optsSignU256.details.error( + "{p}Problem(1) in BLS u256 sign result handler: {err}", + strLogPrefixB, strError ); + } + optsSignU256.details.trace( + "Will call signed-256 answer-sending callback {}, u256 is {}, " + + "glue result is {}", strError ? ( " with error " + log.fmtError( "{err}", strError ) ) : "", + optsSignU256.u256, joGlueResult ); + optsSignU256.fn( strError, optsSignU256.u256, joGlueResult ) + .catch( function( err: Error | string ): void { + optsSignU256.details.critical( + "Problem(2) in BLS u256 sign result handler: {err}", err ); + optsSignU256.errGathering = "Problem(2) in BLS u256 sign result " + + `handler: ${owaspUtils.extractErrorMessage( err )}`; + } ); + return true; +} + +async function gatherSigningCheckOverflow256( + optsSignU256: TSignU256Options ): Promise < boolean > { + if( optsSignU256.joGatheringTracker.nCountReceived < optsSignU256.jarrNodes.length ) + return false; + optsSignU256.fn( + "signature error(2, u256), got " + + `${optsSignU256.joGatheringTracker.nCountErrors} errors(s) for ` + + `${optsSignU256.jarrNodes.length} node(s)`, optsSignU256.u256, null + ).catch( function( err: Error | string ): void { + const cntSuccess = optsSignU256.arrSignResults.length; + optsSignU256.details.critical( + "Problem(3) in BLS u256 sign result handler, not enough successful BLS " + + "signature parts({} when all attempts done, error details: {err}", + cntSuccess, err ); + optsSignU256.errGathering = "Problem(3) in BLS u256 sign result handler, not " + + `enough successful BLS signature parts(${cntSuccess} when all attempts ` + + `done, error details: ${owaspUtils.extractErrorMessage( err )}`; + } ); + return true; +} + +async function doSignU256Gathering( optsSignU256: TSignU256Options ): Promise < void > { + optsSignU256.details.debug( "{p}Waiting for BLS glue result ", optsSignU256.strLogPrefix ); + optsSignU256.errGathering = null; + for( let idxStep = 0; idxStep < optsSignU256.joGatheringTracker.nWaitIntervalMaxSteps; + ++idxStep ) { + await threadInfo.sleep( + optsSignU256.joGatheringTracker.nWaitIntervalStepMilliseconds ); + if( await gatherSigningCheckFinish256( optsSignU256 ) ) + return; + if( await gatherSigningCheckOverflow256( optsSignU256 ) ) + return; + } + // timeout + optsSignU256.fn( + "signature error(3, u256), got " + + `${optsSignU256.joGatheringTracker.nCountErrors} errors(s) for ` + + `${optsSignU256.jarrNodes.length} node(s)`, + optsSignU256.u256, null + ).catch( function( err: Error | string ): void { + const cntSuccess = optsSignU256.arrSignResults.length; + optsSignU256.details.error( + "Problem(4) in BLS u256 sign result handler, not enough successful BLS " + + "signature parts({}) and timeout reached, error details: {err", + cntSuccess, err ); + optsSignU256.errGathering = "Problem(4) in BLS u256 sign result handler, not " + + `enough successful BLS signature parts(${cntSuccess}) and timeout ` + + `reached, error details: ${owaspUtils.extractErrorMessage( err )}`; + } ); +} + +export async function doSignU256( u256: any, details: log.TLogger, + fn: IMA.TFunctionAfterSigningMessages ): Promise < void > { + const optsSignU256: TSignU256Options = { + u256, + fn, + details, + imaState: state.get(), + strLogPrefix: "Sign u256: ", + joGatheringTracker: { + nCountReceivedPrevious: 0, + nCountReceived: 0, + nCountErrors: 0, + nCountSkipped: 0, + nWaitIntervalStepMilliseconds: 500, + nWaitIntervalMaxSteps: 10 * 60 * 3 // 10 is 1 second + }, + arrSignResults: [], + jarrNodes: [], + nThreshold: 1, + nParticipants: 1, + nCountOfBlsPartsToCollect: 1, + errGathering: null + }; + if( !optsSignU256.imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + optsSignU256.jarrNodes = optsSignU256.imaState.joSChainNetworkInfo.network; + optsSignU256.details.trace( "{p}Invoking signing u256 procedure...", + optsSignU256.strLogPrefix ); + optsSignU256.fn = optsSignU256.fn || function(): void {}; + if( !( + optsSignU256.imaState.strPathBlsGlue.length > 0 && + optsSignU256.imaState.joSChainNetworkInfo + ) ) { + optsSignU256.details.warning( "{p}BLS u256 signing is unavailable", + optsSignU256.strLogPrefix ); + await optsSignU256.fn( "BLS u256 signing is unavailable", optsSignU256.u256, null ); + return; + } + if( !( await prepareSignU256( optsSignU256 ) ) ) + return; + for( let i = 0; i < optsSignU256.jarrNodes.length; ++i ) + await doSignU256OneImpl( i, optsSignU256 ); + await doSignU256Gathering( optsSignU256 ); + optsSignU256.details.trace( "Will await BLS u256 sign result..." ); + if( optsSignU256.errGathering ) { + optsSignU256.details.error( "Failed BLS u256 sign result awaiting: {err}", + optsSignU256.errGathering.toString() ); + return; + } + optsSignU256.details.information( "{p}Completed signing u256 procedure", + optsSignU256.strLogPrefix ); +} + +export async function doVerifyReadyHash( + strMessageHash: string, nZeroBasedNodeIndex: number, + signature: any, isExposeOutput: boolean +): Promise < boolean > { + const imaState: state.TIMAState = state.get(); + const strDirection = "RAW"; + const strLogPrefix = `${strDirection}/BLS/#${nZeroBasedNodeIndex}: `; + const details = log.createMemoryStream(); + let isSuccess = false; + const arrTmp = signature.signatureShare.split( ":" ); + const joResultFromNode: any = { + index: nZeroBasedNodeIndex.toString(), + signature: { + X: arrTmp[0], + Y: arrTmp[1] + } + }; + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const nThreshold = discoverBlsThreshold( imaState.joSChainNetworkInfo ); + const nParticipants = discoverBlsParticipants( imaState.joSChainNetworkInfo ); + if( !checkBlsThresholdAndBlsParticipants( + nThreshold, nParticipants, "verify ready hash", details ) ) + return false; + const strActionDir = allocBlsTmpActionDir(); + const fnShellRestore = function(): void { shell.rm( "-rf", strActionDir ); }; + let strOutput = ""; + try { + const joPublicKey = discoverPublicKeyByIndex( + nZeroBasedNodeIndex, imaState.joSChainNetworkInfo, details, true ); + details.trace( "{p}BLS node #{} - hashed verify message is {}", + strLogPrefix, nZeroBasedNodeIndex, strMessageHash ); + const joMsg = { message: strMessageHash }; + details.debug( "{p}BLS node #{} - composed {} using hash {} and glue {} and public key {}", + strLogPrefix, nZeroBasedNodeIndex, joMsg, strMessageHash, + joResultFromNode, joPublicKey ); + const strSignResultFileName = + strActionDir + "/sign-result" + nZeroBasedNodeIndex + ".json"; + imaUtils.jsonFileSave( strSignResultFileName, joResultFromNode ); + imaUtils.jsonFileSave( strActionDir + "/hash.json", joMsg ); + imaUtils.jsonFileSave( + strActionDir + "/BLS_keys" + nZeroBasedNodeIndex + ".json", joPublicKey ); + const strVerifyCommand = + imaState.strPathBlsVerify + + " --t " + nThreshold + + " --n " + nParticipants + + " --j " + nZeroBasedNodeIndex + + " --input " + strSignResultFileName; + details.trace( "{p}Will execute node #{} BLS verify command: {}", + strLogPrefix, nZeroBasedNodeIndex, strVerifyCommand ); + strOutput = childProcessModule.execSync( strVerifyCommand, { cwd: strActionDir } ) + .toString( "utf8" ); + details.trace( "{p}BLS node #{} verify output is:\n{raw}", strLogPrefix, + nZeroBasedNodeIndex, strOutput || "<>" ); + details.success( "{p}BLS node #{} verify success", strLogPrefix, nZeroBasedNodeIndex ); + fnShellRestore(); + isSuccess = true; + } catch ( err ) { + details.critical( "{p}BLS node #{} verify error, error description is: {err}" + + ", stack is:\n{stack}", strLogPrefix, nZeroBasedNodeIndex, err, err ); + details.critical( "{p}BLS node #{} verify output is:\n{raw}", + strLogPrefix, nZeroBasedNodeIndex, strOutput || "<>" ); + fnShellRestore(); + isSuccess = false; + } + if( isExposeOutput || ( !isSuccess ) ) + details.exposeDetailsTo( log.globalStream(), "BLS-raw-verifier", isSuccess ); + details.close(); + return isSuccess; +} + +async function doSignReadyHashHandleCallResult( + strLogPrefix: string, details: log.TLogger, + strMessageHash: string, isExposeOutput: boolean, joCall: rpcCall.TRPCCall, + joIn: any, joOut: any +): Promise < object > { + details.trace( "{p}Call to ", "SGX done, answer is: {}", strLogPrefix, joOut ); + let joSignResult: TSignResult = joOut; + if( joOut.result != null && joOut.result != undefined && + typeof joOut.result === "object" ) + joSignResult = joOut.result; + if( joOut.signResult != null && joOut.signResult != undefined && + typeof joOut.signResult === "object" ) + joSignResult = joOut.signResult; + if( !joSignResult ) { + const strError = "No signature arrived"; + details.error( "{p}BLS-sign(1) finished with error: {err}", strLogPrefix, strError ); + await joCall.disconnect(); + throw new Error( strError ); + } + if( "errorMessage" in joSignResult && + typeof joSignResult.errorMessage === "string" && + joSignResult.errorMessage.length > 0 + ) { + const strError = `BLS-sign finished with error: ${joSignResult.errorMessage};`; + details.error( "{p}BLS-sign(2) finished with error: {err}", + strLogPrefix, joSignResult.errorMessage ); + await joCall.disconnect(); + throw new Error( strError ); + } + joSignResult.error = null; + await joCall.disconnect(); + return joSignResult; +} + +export async function doSignReadyHash( + strMessageHash: string, isExposeOutput: any ): Promise < TSignResult | null > { + const imaState: state.TIMAState = state.get(); + const strLogPrefix = ""; + const details: log.TLogger = log.createMemoryStream(); + let joSignResult: TSignResult | null = null; + let joCall: rpcCall.TRPCCall | null = null; + try { + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const nThreshold = discoverBlsThreshold( imaState.joSChainNetworkInfo ); + const nParticipants = discoverBlsParticipants( imaState.joSChainNetworkInfo ); + details.debug( "{p}Will BLS-sign ready hash.", strLogPrefix ); + details.trace( "{p}Discovered BLS threshold is {}.", strLogPrefix, nThreshold ); + details.trace( "{p}Discovered number of BLS participants is {}.", + strLogPrefix, nParticipants ); + details.trace( "{p}hash value to sign is {}", strLogPrefix, strMessageHash ); + if( !checkBlsThresholdAndBlsParticipants( + nThreshold, nParticipants, "sign ready hash", details ) ) + return null; + let joAccount: state.TAccount = imaState.chainProperties.sc.joAccount; + if( !joAccount.strSgxURL ) { + joAccount = imaState.chainProperties.mn.joAccount; + if( !joAccount.strSgxURL ) + throw new Error( "SGX URL is unknown, cannot sign U256" ); + if( !joAccount.strBlsKeyName ) + throw new Error( "BLS keys name is unknown, cannot sign U256" ); + } + let rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + if( "strPathSslKey" in joAccount && typeof joAccount.strPathSslKey === "string" && + joAccount.strPathSslKey.length > 0 && "strPathSslCert" in joAccount && + typeof joAccount.strPathSslCert === "string" && joAccount.strPathSslCert.length > 0 + ) { + rpcCallOpts = { + cert: fs.readFileSync( joAccount.strPathSslCert, "utf8" ), + key: fs.readFileSync( joAccount.strPathSslKey, "utf8" ) + }; + } else + details.warning( "Will sign via SGX without SSL options" ); + joCall = await rpcCall.create( joAccount.strSgxURL, rpcCallOpts ); + if( !joCall ) + throw new Error( `Failed to create JSON RPC call object to ${joAccount.strSgxURL}` ); + const joIn: any = { + jsonrpc: "2.0", + id: utils.randomCallID(), + method: "blsSignMessageHash", + params: { + keyShareName: joAccount.strBlsKeyName, + messageHash: strMessageHash, + n: nParticipants, + t: nThreshold + } + }; + details.trace( "{p}Will invoke SGX with call data {}", strLogPrefix, joIn ); + const joOut = await joCall.call( joIn ); + joSignResult = await doSignReadyHashHandleCallResult( + strLogPrefix, details, strMessageHash, isExposeOutput, joCall, joIn, joOut ); + } catch ( err ) { + const strError = owaspUtils.extractErrorMessage( err ); + joSignResult = { }; + joSignResult.error = strError; + details.error( "{p}JSON RPC call to SGX failed, error is: {err}, stack is:\n{stack}", + strLogPrefix, err, err ); + if( joCall ) + await joCall.disconnect(); + } + const isSuccess = !!( ( + joSignResult && typeof joSignResult === "object" && ( !joSignResult.error ) ) ); + if( isExposeOutput || ( !isSuccess ) ) + details.exposeDetailsTo( log.globalStream(), "BLS-raw-signer", isSuccess ); + details.close(); + return joSignResult; +} + +async function prepareHandlingOfSkaleImaVerifyAndSign( + optsHandleVerifyAndSign: THandleVerifyAndSignOptions ): Promise < boolean > { + optsHandleVerifyAndSign.details.debug( "{p}Will verify and sign {}", + optsHandleVerifyAndSign.strLogPrefix, optsHandleVerifyAndSign.joCallData ); + optsHandleVerifyAndSign.nIdxCurrentMsgBlockStart = + optsHandleVerifyAndSign.joCallData.params.startMessageIdx; + optsHandleVerifyAndSign.strFromChainName = + optsHandleVerifyAndSign.joCallData.params.srcChainName; + optsHandleVerifyAndSign.strToChainName = + optsHandleVerifyAndSign.joCallData.params.dstChainName; + optsHandleVerifyAndSign.strFromChainID = + optsHandleVerifyAndSign.joCallData.params.srcChainID; + optsHandleVerifyAndSign.strToChainID = + optsHandleVerifyAndSign.joCallData.params.dstChainID; + optsHandleVerifyAndSign.strDirection = + optsHandleVerifyAndSign.joCallData.params.direction; + optsHandleVerifyAndSign.jarrMessages = + optsHandleVerifyAndSign.joCallData.params.messages; + optsHandleVerifyAndSign.details.trace( + "{p}{bright} verification algorithm will work for transfer from chain {}/{} to " + + "chain {}/{} and work with array of message(s) {}", + optsHandleVerifyAndSign.strLogPrefix, optsHandleVerifyAndSign.strDirection, + optsHandleVerifyAndSign.strFromChainName, optsHandleVerifyAndSign.strFromChainID, + optsHandleVerifyAndSign.strToChainName, optsHandleVerifyAndSign.strToChainID, + optsHandleVerifyAndSign.jarrMessages ); + if( !optsHandleVerifyAndSign.imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + optsHandleVerifyAndSign.nThreshold = + discoverBlsThreshold( optsHandleVerifyAndSign.imaState.joSChainNetworkInfo ); + optsHandleVerifyAndSign.nParticipants = + discoverBlsParticipants( optsHandleVerifyAndSign.imaState.joSChainNetworkInfo ); + optsHandleVerifyAndSign.details.debug( + "{p}{bright} verification algorithm discovered BLS threshold is {}.", + optsHandleVerifyAndSign.strLogPrefix, optsHandleVerifyAndSign.strDirection, + optsHandleVerifyAndSign.nThreshold ); + optsHandleVerifyAndSign.details.debug( + "{p}{bright} verification algorithm discovered number of BLS participants is {}.", + optsHandleVerifyAndSign.strLogPrefix, optsHandleVerifyAndSign.strDirection, + optsHandleVerifyAndSign.nParticipants ); + if( !checkBlsThresholdAndBlsParticipants( + optsHandleVerifyAndSign.nThreshold, + optsHandleVerifyAndSign.nParticipants, + "prepare handling of skale_imaVerifyAndSign", + optsHandleVerifyAndSign.details ) ) + return false; + optsHandleVerifyAndSign.strMessageHash = owaspUtils.removeStarting0x( keccak256Message( + optsHandleVerifyAndSign.jarrMessages, + optsHandleVerifyAndSign.nIdxCurrentMsgBlockStart, + optsHandleVerifyAndSign.strFromChainName + ) ); + optsHandleVerifyAndSign.details.debug( + "{p}{bright} verification algorithm message hash to sign is {}", + optsHandleVerifyAndSign.strLogPrefix, optsHandleVerifyAndSign.strDirection, + optsHandleVerifyAndSign.strMessageHash ); + return true; +} + +async function prepareS2sOfSkaleImaVerifyAndSign( + optsHandleVerifyAndSign: THandleVerifyAndSignOptions ): Promise < void > { + const strSChainNameSrc = optsHandleVerifyAndSign.joCallData.params.srcChainName; + const strSChainNameDst = optsHandleVerifyAndSign.joCallData.params.dstChainName; + optsHandleVerifyAndSign.details.trace( + "{p}{bright} verification algorithm will use for source chain name {} and destination " + + "chain name {}", optsHandleVerifyAndSign.strLogPrefix, + optsHandleVerifyAndSign.strDirection, strSChainNameSrc, strSChainNameDst ); + const arrSChainsCached = skaleObserver.getLastCachedSChains(); + if( ( !arrSChainsCached ) || arrSChainsCached.length == 0 ) { + throw new Error( `Could not handle ${optsHandleVerifyAndSign.strDirection} ` + + "skale_imaVerifyAndSign(1), no S-Chains in SKALE NETWORK observer cached yet, " + + "try again later" ); + } + + let joSChainSrc: any = null; let strUrlSrcSChain: string | null = null; + for( let idxSChain = 0; idxSChain < arrSChainsCached.length; ++idxSChain ) { + const joSChain = arrSChainsCached[idxSChain]; + if( joSChain.name.toString() == strSChainNameSrc.toString() ) { + joSChainSrc = joSChain; + strUrlSrcSChain = skaleObserver.pickRandomSChainUrl( joSChain ); + break; + } + } + if( joSChainSrc == null || strUrlSrcSChain == null || strUrlSrcSChain.length == 0 ) { + throw new Error( `Could not handle ${optsHandleVerifyAndSign.strDirection} ` + + "skale_imaVerifyAndSign(2), failed to discover source chain access parameters, " + + "try again later" ); + } + optsHandleVerifyAndSign.details.trace( + "{p}{bright} verification algorithm discovered source chain URL is {url}, chain name " + + "is {}, chain id is {}", optsHandleVerifyAndSign.strLogPrefix, + optsHandleVerifyAndSign.strDirection, strUrlSrcSChain, + joSChainSrc.name, joSChainSrc.chainId ); + optsHandleVerifyAndSign.joExtraSignOpts = { + ethersProviderSrc: owaspUtils.getEthersProviderFromURL( strUrlSrcSChain ), + chainNameSrc: optsHandleVerifyAndSign.strFromChainName, + chainNameDst: optsHandleVerifyAndSign.strToChainName, + chainIdSrc: optsHandleVerifyAndSign.strFromChainID, + chainIdDst: optsHandleVerifyAndSign.strToChainID + }; +} + +async function handleBlsSignMessageHashResult( + optsHandleVerifyAndSign: THandleVerifyAndSignOptions, joCallData: THandleVerifyAndSignCallData, + joAccount: state.TAccount, joCall: rpcCall.TRPCCall, joIn: any, joOut: any +): Promise < TSignResult > { + optsHandleVerifyAndSign.details.trace( "{p}{bright} Call to SGX done, " + + "answer is: {}", optsHandleVerifyAndSign.strLogPrefix, + optsHandleVerifyAndSign.strDirection, joOut ); + let joSignResult: TSignResult = joOut; + if( joOut.result != null && joOut.result != undefined && + typeof joOut.result === "object" ) + joSignResult = joOut.result; + if( joOut.signResult != null && joOut.signResult != undefined && + typeof joOut.signResult === "object" ) + joSignResult = joOut.signResult; + if( "qa" in optsHandleVerifyAndSign.joCallData.params ) + optsHandleVerifyAndSign.joRetVal.qa = optsHandleVerifyAndSign.joCallData.params.qa; + if( !joSignResult ) { + const strError = "No signature arrived"; + optsHandleVerifyAndSign.joRetVal.error = strError; + optsHandleVerifyAndSign.details.error( + "{p}BLS-sign(1) finished with error: {err}", + optsHandleVerifyAndSign.strLogPrefix, strError ); + await joCall.disconnect(); + throw new Error( strError ); + } + if( "errorMessage" in joSignResult && + typeof joSignResult.errorMessage === "string" && + joSignResult.errorMessage.length > 0 + ) { + optsHandleVerifyAndSign.isSuccess = false; + const strError = `BLS-sign finished with error: ${joSignResult.errorMessage};`; + optsHandleVerifyAndSign.joRetVal.error = strError; + optsHandleVerifyAndSign.details.error( + "{p}BLS-sign(2) finished with error: {err}", + optsHandleVerifyAndSign.strLogPrefix, joSignResult.errorMessage ); + await joCall.disconnect(); + throw new Error( strError ); + } + optsHandleVerifyAndSign.isSuccess = true; + optsHandleVerifyAndSign.joRetVal.result = { signResult: joSignResult }; + if( "qa" in optsHandleVerifyAndSign.joCallData.params ) + optsHandleVerifyAndSign.joRetVal.qa = optsHandleVerifyAndSign.joCallData.params.qa; + await joCall.disconnect(); + return joSignResult; +} + +export async function handleSkaleImaVerifyAndSign( + joCallData: THandleVerifyAndSignCallData ): Promise < object | null > { + const optsHandleVerifyAndSign: THandleVerifyAndSignOptions = { + joCallData, + imaState: state.get(), + strLogPrefix: "", + details: log.createMemoryStream(), + joRetVal: {}, + isSuccess: false, + nIdxCurrentMsgBlockStart: 0, + strFromChainName: "", + strToChainName: "", + strFromChainID: "", + strToChainID: "", + strDirection: "", + jarrMessages: [], + strMessageHash: "", + joExtraSignOpts: null, + nThreshold: 1, + nParticipants: 1 + }; + let joCall: rpcCall.TRPCCall | null = null; + try { + if( !( await prepareHandlingOfSkaleImaVerifyAndSign( optsHandleVerifyAndSign ) ) ) + return null; + optsHandleVerifyAndSign.joExtraSignOpts = null; + if( optsHandleVerifyAndSign.strDirection == "S2S" ) + await prepareS2sOfSkaleImaVerifyAndSign( optsHandleVerifyAndSign ); + + await checkCorrectnessOfMessagesToSign( + optsHandleVerifyAndSign.details, optsHandleVerifyAndSign.strLogPrefix, + optsHandleVerifyAndSign.strDirection, optsHandleVerifyAndSign.jarrMessages, + optsHandleVerifyAndSign.nIdxCurrentMsgBlockStart, + optsHandleVerifyAndSign.joExtraSignOpts + ); + optsHandleVerifyAndSign.details.debug( "{p}Will BLS-sign verified messages.", + optsHandleVerifyAndSign.strLogPrefix ); + let joAccount = optsHandleVerifyAndSign.imaState.chainProperties.sc.joAccount; + if( !joAccount.strSgxURL ) { + joAccount = optsHandleVerifyAndSign.imaState.chainProperties.mn.joAccount; + if( !joAccount.strSgxURL ) + throw new Error( "SGX URL is unknown, cannot sign(handle) IMA message(s)" ); + if( !joAccount.strBlsKeyName ) + throw new Error( "BLS keys name is unknown, cannot sign IMA message(s)" ); + } + let rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + if( "strPathSslKey" in joAccount && typeof joAccount.strPathSslKey === "string" && + joAccount.strPathSslKey.length > 0 && "strPathSslCert" in joAccount && + typeof joAccount.strPathSslCert === "string" && joAccount.strPathSslCert.length > 0 + ) { + rpcCallOpts = { + cert: fs.readFileSync( joAccount.strPathSslCert, "utf8" ), + key: fs.readFileSync( joAccount.strPathSslKey, "utf8" ) + }; + } else + optsHandleVerifyAndSign.details.warning( "Will sign via SGX without SSL options" ); + joCall = await rpcCall.create( joAccount.strSgxURL, rpcCallOpts ); + if( !joCall ) + throw new Error( `Failed to create JSON RPC call object to ${joAccount.strSgxURL}` ); + const joIn = { + jsonrpc: "2.0", + id: utils.randomCallID(), + method: "blsSignMessageHash", + params: { + keyShareName: joAccount.strBlsKeyName, + messageHash: optsHandleVerifyAndSign.strMessageHash, + n: optsHandleVerifyAndSign.nParticipants, + t: optsHandleVerifyAndSign.nThreshold + } + }; + optsHandleVerifyAndSign.details.trace( + "{p}{bright} verification algorithm will invoke SGX with call data {}", + optsHandleVerifyAndSign.strLogPrefix, optsHandleVerifyAndSign.strDirection, joIn ); + const joOut = await joCall.call( joIn ); + await handleBlsSignMessageHashResult( + optsHandleVerifyAndSign, joCallData, joAccount, joCall, joIn, joOut ); + } catch ( err ) { + optsHandleVerifyAndSign.details.error( + "{p}{bright}JSON RPC call(handleSkaleImaVerifyAndSign) " + + "to SGX failed, RPC call failed, error is: {err}", + optsHandleVerifyAndSign.strLogPrefix, optsHandleVerifyAndSign.strDirection, err ); + if( joCall ) + await joCall.disconnect(); + throw new Error( "JSON RPC call(handleSkaleImaVerifyAndSign) to SGX failed, " + + `RPC call failed, error is: ${owaspUtils.extractErrorMessage( err )}` ); + } + optsHandleVerifyAndSign.details.exposeDetailsTo( + log.globalStream(), "IMA messages verifier/signer", optsHandleVerifyAndSign.isSuccess ); + optsHandleVerifyAndSign.details.close(); + return optsHandleVerifyAndSign.joRetVal; +} + +async function handleSkaleImaBSU256Prepare( optsBSU256: TBSU256Options ): Promise < boolean > { + optsBSU256.details.debug( "{p}Will U256-BLS-sign {}", + optsBSU256.strLogPrefix, optsBSU256.joCallData ); + if( !optsBSU256.imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + optsBSU256.nThreshold = discoverBlsThreshold( optsBSU256.imaState.joSChainNetworkInfo ); + optsBSU256.nParticipants = discoverBlsParticipants( optsBSU256.imaState.joSChainNetworkInfo ); + optsBSU256.details.trace( "{p}Discovered BLS threshold is {}.", + optsBSU256.strLogPrefix, optsBSU256.nThreshold ); + optsBSU256.details.trace( "{p}Discovered number of BLS participants is {}.", + optsBSU256.strLogPrefix, optsBSU256.nParticipants ); + if( !checkBlsThresholdAndBlsParticipants( + optsBSU256.nThreshold, + optsBSU256.nParticipants, + "handle BSU256Prepare", + optsBSU256.details ) ) + return false; + optsBSU256.u256 = optsBSU256.joCallData.params.valueToSign; + optsBSU256.details.trace( "{p}U256 original value is {}", + optsBSU256.strLogPrefix, optsBSU256.u256 ); + optsBSU256.strMessageHash = keccak256U256( optsBSU256.u256, true ); + optsBSU256.details.trace( "{p}hash of U256 value to sign is {}", + optsBSU256.strLogPrefix, optsBSU256.strMessageHash ); + optsBSU256.details.trace( "{p}Will BLS-sign U256.", optsBSU256.strLogPrefix ); + if( !optsBSU256.joAccount ) + throw new Error( "No account to perform blsSignMessageHash for U256" ); + optsBSU256.joAccount = optsBSU256.imaState.chainProperties.sc.joAccount; + if( !optsBSU256.joAccount.strSgxURL ) { + optsBSU256.joAccount = optsBSU256.imaState.chainProperties.mn.joAccount; + if( !optsBSU256.joAccount.strSgxURL ) + throw new Error( "SGX URL is unknown, cannot sign U256" ); + if( !optsBSU256.joAccount.strBlsKeyName ) + throw new Error( "BLS keys name is unknown, cannot sign U256" ); + } + return true; +} + +async function handleBlsSignMessageHash256Result( + optsBSU256: TBSU256Options, joCallData: TBSU256CallData, + joCall: rpcCall.TRPCCall, joIn: any, joOut: any +): Promise < object > { + optsBSU256.details.trace( "{p}Call to SGX done, answer is: {}", + optsBSU256.strLogPrefix, joOut ); + let joSignResult: TSignResult = joOut; + if( joOut.result != null && joOut.result != undefined && + typeof joOut.result === "object" ) + joSignResult = joOut.result; + if( joOut.signResult != null && joOut.signResult != undefined && + typeof joOut.signResult === "object" ) + joSignResult = joOut.signResult; + if( !joSignResult ) { + const strError = "No signature arrived"; + optsBSU256.joRetVal.error = strError; + optsBSU256.details.error( + "{p}U256/BLS-sign(1) finished with error: {err}", optsBSU256.strLogPrefix, strError ); + await joCall.disconnect(); + throw new Error( strError ); + } + if( "errorMessage" in joSignResult && + typeof joSignResult.errorMessage === "string" && + joSignResult.errorMessage.length > 0 ) { + optsBSU256.isSuccess = false; + const strError = "BLS-sign finished with " + + `error: ${joSignResult.errorMessage}`; + optsBSU256.joRetVal.error = strError; + optsBSU256.details.error( "{p}U256/BLS-sign(2) finished with error: {err}", + optsBSU256.strLogPrefix, joSignResult.errorMessage ); + await joCall.disconnect(); + throw new Error( strError ); + } + optsBSU256.isSuccess = true; + optsBSU256.joRetVal.result = { signResult: joSignResult }; + if( "qa" in optsBSU256.joCallData.params ) + optsBSU256.joRetVal.qa = optsBSU256.joCallData.params.qa; + await joCall.disconnect(); + return joSignResult; +} + +export async function handleSkaleImaBSU256( + joCallData: TBSU256CallData ): Promise < object | null > { + const optsBSU256: TBSU256Options = { + joCallData, + imaState: state.get(), + strLogPrefix: "", + details: log.createMemoryStream(), + joRetVal: {}, + isSuccess: false, + nThreshold: 1, + nParticipants: 1, + u256: null, + strMessageHash: "", + joAccount: null + }; + let joCall: rpcCall.TRPCCall | null = null; + try { + if( !( await handleSkaleImaBSU256Prepare( optsBSU256 ) ) ) + return null; + if( !optsBSU256.joAccount ) + throw new Error( "No account to perform blsSignMessageHash for U256" ); + let rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + if( "strPathSslKey" in optsBSU256.joAccount && + typeof optsBSU256.joAccount.strPathSslKey === "string" && + optsBSU256.joAccount.strPathSslKey.length > 0 && + "strPathSslCert" in optsBSU256.joAccount && + typeof optsBSU256.joAccount.strPathSslCert === "string" && + optsBSU256.joAccount.strPathSslCert.length > 0 + ) { + rpcCallOpts = { + cert: fs.readFileSync( optsBSU256.joAccount.strPathSslCert, "utf8" ), + key: fs.readFileSync( optsBSU256.joAccount.strPathSslKey, "utf8" ) + }; + } else + optsBSU256.details.warning( "Will sign via SGX without SSL options" ); + joCall = await rpcCall.create( optsBSU256.joAccount.strSgxURL, rpcCallOpts ); + if( !joCall ) { + throw new Error( "Failed to create JSON RPC call object " + + `to ${optsBSU256.joAccount.strSgxURL}` ); + } + const joIn = { + jsonrpc: "2.0", + id: utils.randomCallID(), + method: "blsSignMessageHash", + params: { + keyShareName: optsBSU256.joAccount.strBlsKeyName, + messageHash: optsBSU256.strMessageHash, + n: optsBSU256.nParticipants, + t: optsBSU256.nThreshold + } + }; + optsBSU256.details.trace( "{p}Will invoke SGX with call data {}", + optsBSU256.strLogPrefix, joIn ); + const joOut = await joCall.call( joIn ); + await handleBlsSignMessageHash256Result( optsBSU256, joCallData, joCall, joIn, joOut ); + } catch ( err ) { + optsBSU256.isSuccess = false; + const strError = owaspUtils.extractErrorMessage( err ); + optsBSU256.joRetVal.error = strError; + optsBSU256.details.error( + "{p}JSON RPC call(handleSkaleImaBSU256) to SGX failed, " + + "RPC call failed, error is: {err}", optsBSU256.strLogPrefix, err ); + if( joCall ) + await joCall.disconnect(); + throw new Error( "JSON RPC call(handleSkaleImaBSU256) to SGX failed, " + + `RPC call failed, error is: ${owaspUtils.extractErrorMessage( err )}` ); + } + optsBSU256.details.exposeDetailsTo( + log.globalStream(), "U256-BLS-signer", optsBSU256.isSuccess ); + optsBSU256.details.close(); + return optsBSU256.joRetVal; +} diff --git a/src/cc.ts b/src/cc.ts new file mode 100644 index 00000000..29149b58 --- /dev/null +++ b/src/cc.ts @@ -0,0 +1,1032 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file cc.ts + * @copyright SKALE Labs 2019-Present + */ + +let gFlagIsEnabled: boolean = false; + +export function autoEnableFromCommandLineArgs(): void { + const b: boolean = + !!( ( process.argv.includes( "--colors" ) || process.argv.includes( "-colors" ) ) ); + enable( b ); +} + +export function enable( b?: boolean ): void { + gFlagIsEnabled = !!b; +} + +export function isStringAlreadyColorized( s?: any ): boolean { + if( s && typeof s === "string" && s.length > 0 && s[0] == "\x1b" ) + return true; + return false; +} + +export function isEnabled(): boolean { + return !!gFlagIsEnabled; +} + +export function replaceAll( str: string, find: string, replace: string ): string { + return str.replace( new RegExp( find, "g" ), replace ); +} + +export function validateRadix( value: any, radix?: any ): boolean { + value = ( value ? value.toString() : "10" ).trim(); + radix = ( radix === null || radix === undefined ) + ? ( ( value.length > 2 && value[0] == "0" && ( value[1] == "x" || value[1] == "X" ) ) + ? 16 + : 10 ) + : parseInt( radix, 10 ); + return radix; +} + +export function validateInteger( value: any, radix?: any ): boolean { + try { + if( value === null || value === undefined ) + return false; + if( value === 0 || value === 0.0 ) + return true; + const s = value ? value.toString().trim() : ""; + if( s.length < 1 ) + return false; + radix = validateRadix( value, radix ); + if( ( !isNaN( value ) ) && + ( parseInt( value, radix ) == value || radix !== 10 ) && + ( !isNaN( parseInt( value, radix ) ) ) + ) + return true; + } catch ( err ) { + } + return false; +} + +export function toInteger( value: any, radix?: any ): number { + try { + if( value === 0 || value === 0.0 || value === null || value === undefined ) + return 0; + value = ( value ? value.toString().trim() : "" ).trim(); + radix = validateRadix( value, radix ); + if( !validateInteger( value, radix ) ) + return NaN; + return parseInt( value.toString().trim(), radix ); + } catch ( err ) { + } + return 0; +} + +export function validateFloat( value: any ): boolean { + try { + if( value === null || value === undefined ) + return false; + if( value === 0 || value === 0.0 ) + return true; + const f = parseFloat( value.toString().trim() ); + if( isNaN( f ) ) + return false; + return true; + } catch ( err ) { + } + return false; +} + +export function toFloat( value: any ): number { + try { + if( value === 0 || value === 0.0 || value === null || value === undefined ) + return 0.0; + const f = parseFloat( value.toString().trim() ); + return f; + } catch ( err ) { + } + return 0.0; +} + +export function toBoolean( value?: any ): boolean { + let b = false; + try { + if( typeof value === "boolean" ) + return value; + if( typeof value === "string" ) { + const ch = value[0].toLowerCase(); + if( ch == "y" || ch == "t" ) + b = true; + else if( /^-?\d+$/.test( value ) ) // check string is integer + b = !!parseInt( value, 10 ); + else if( /^-?\d+(?:[.,]\d*?)?$/.test( value ) ) // check string is float + b = !!toFloat( value ); else + b = !!b; + } else + b = !!b; + } catch ( err ) { + b = false; + } + b = !!b; + return b; +} + +export function yn( flag?: any ): string { + if( !gFlagIsEnabled ) + return flag ? "true" : "false"; + return toBoolean( flag ) ? yes( "yes" ) : no( "no" ); +} + +export function tf( flag?: any ): string { + if( !gFlagIsEnabled ) + return flag ? "true" : "false"; + return toBoolean( flag ) ? yes( "true" ) : no( "false" ); +} + +export function onOff( flag?: any ): string { + if( !gFlagIsEnabled ) + return flag ? "true" : "false"; + return toBoolean( flag ) ? yes( "on" ) : no( "off" ); +} +const gMapColorDefinitions: any = { + reset: "\x1b[0m", + enlight: "\x1b[1m", + dim: "\x1b[2m", + underscore: "\x1b[4m", + blink: "\x1b[5m", + reverse: "\x1b[7m", + hidden: "\x1b[8m", + fgBlack: "\x1b[30m", + fgRed: "\x1b[31m", + fgGreen: "\x1b[32m", + fgYellow: "\x1b[33m", + fgBlue: "\x1b[34m", + fgMagenta: "\x1b[35m", + fgCyan: "\x1b[36m", + fgWhite: "\x1b[37m", + bgBlack: "\x1b[40m", + bgRed: "\x1b[41m", + bgGreen: "\x1b[42m", + bgYellow: "\x1b[43m", + bgBlue: "\x1b[44m", + bgMagenta: "\x1b[45m", + bgCyan: "\x1b[46m", + bBgWhite: "\x1b[47m" +}; + +const gArrRainbowParts: any[] = [ + gMapColorDefinitions.enlight + gMapColorDefinitions.fgRed, + gMapColorDefinitions.fgRed, + gMapColorDefinitions.enlight + gMapColorDefinitions.fgYellow, + gMapColorDefinitions.fgYellow, + gMapColorDefinitions.enlight + gMapColorDefinitions.fgGreen, + gMapColorDefinitions.fgGreen, + gMapColorDefinitions.enlight + gMapColorDefinitions.fgCyan, + gMapColorDefinitions.fgCyan, + gMapColorDefinitions.enlight + gMapColorDefinitions.fgBlue, + gMapColorDefinitions.fgBlue, + gMapColorDefinitions.enlight + gMapColorDefinitions.fgMagenta, + gMapColorDefinitions.fgMagenta +]; + +export function rainbowPart( s: string, i: number ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + const j = i % gArrRainbowParts.length; + return gArrRainbowParts[j] + s + gMapColorDefinitions.reset; +} + +export function rainbow( s?: any ): string { + if( ( !gFlagIsEnabled ) || ( !s ) || ( typeof s !== "string" ) || s.length == 0 ) + return s ? s.toString() : JSON.stringify( s ); + let res = ""; + const cnt = s.length; + for( let i = 0; i < cnt; ++i ) + res = res + rainbowPart( s[i], i ); + return res; +} + +export function isInt2( n?: any ): boolean { + const intRegex = /^-?\d+$/; + if( !intRegex.test( n ) ) + return false; + + const intVal = parseInt( n, 10 ); + return parseFloat( n ) == intVal && !isNaN( intVal ); +} + +export function isFloat2( n?: any ): boolean { + const val = parseFloat( n ); + return !isNaN( val ); +} + +function urlObjColorized( objURL?: any ): string { + let strURL = ""; + if( !objURL ) + return strURL; + if( objURL.protocol && objURL.protocol !== null && objURL.protocol !== undefined ) + strURL += yellow( objURL.protocol ) + normal( "//" ); + if( objURL.username && objURL.username !== null && objURL.username !== undefined ) { + strURL += magenta( objURL.username ); + if( objURL.password && objURL.password !== null && objURL.password !== undefined ) + strURL += normal( ":" ) + yellow( objURL.password ); + strURL += normal( "@" ); + } + if( objURL.hostname ) + strURL += magenta( logArgToStringAsIpv4( objURL.hostname ) ); + if( objURL.port && objURL.port !== null && objURL.port !== undefined ) + strURL += normal( ":" ) + logArgToString( objURL.port ); + if( objURL.pathname && objURL.pathname !== null && + objURL.pathname !== undefined && objURL.pathname !== "/" ) + strURL += yellow( replaceAll( objURL.pathname, "/", normal( "/" ) ) ); + if( objURL.search && objURL.search !== null && objURL.search !== undefined ) + strURL += magenta( objURL.search ); + return strURL; +} + +export function urlStrColorized( s?: any ): string { + const objURL = safeURL( s ); + if( !objURL ) + return ""; + return urlObjColorized( objURL ); +} + +export function urlColorized( x?: any ): string { + if( typeof x === "string" || x instanceof String ) + return urlStrColorized( x ); + return urlObjColorized( x ); +} + +export function u( x?: any ): string { + return urlColorized( x ); +} + +export function safeURL( arg?: any ): URL | null { + try { + const sc = arg[0]; + if( sc == "\"" || sc == "'" ) { + const cnt = arg.length; + if( arg[cnt - 1] == sc ) { + const ss = arg.substring( 1, cnt - 1 ); + const objURL = safeURL( ss ); + if( objURL != null && objURL != undefined ) { + const anyURL: any = objURL; + anyURL.strStrippedStringComma = sc; + } + + return objURL; + } + return null; + } + const objURL = new URL( arg ); + if( !objURL.hostname ) + return null; + + if( objURL.hostname.length === 0 ) + return null; + + const anyURL: any = objURL; + anyURL.strStrippedStringComma = null; + return objURL; + } catch ( err ) { + return null; + } +} + +export function toIpv4Arr( s: string ): any[] | null { + // eslint-disable-next-line max-len + if( /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( s ) ) { + const arr = s.split( "." ); + if( ( !arr ) || arr.length !== 4 ) + return null; + + return arr; + } + return null; +} + +export function logArgToStringAsIpv4( arg?: any ): string { + const arr = toIpv4Arr( arg ); + if( !arr ) + return arg.toString(); + let s = ""; + for( let i = 0; i < 4; ++i ) { + if( i > 0 ) + s += normal( "." ); + + s += logArgToString( arr[i] ); + } + return s; +} + +export function logArgToString( ...args: any[] ): string { + let i; + const cnt = arguments.length; + let s = ""; + for( i = 0; i < cnt; ++i ) { + const arg = arguments[i]; + if( arg === undefined ) { + s += undefval( arg ); + continue; + } + if( arg === null ) { + s += nullval( arg ); + continue; + } + if( isNaN( arg ) ) { + s += nanval( arg ); + continue; + } + if( typeof arg === "boolean" ) { + s += tf( arg ); + continue; + } + if( typeof arg === "object" && typeof arg.valueOf() === "boolean" ) + s += tf( arg.valueOf() ); + + if( typeof arg === "number" || typeof arg === "bigint" ) { + s += number( arg ); + continue; + } + if( typeof arg === "object" && + ( typeof arg.valueOf() === "number" || typeof arg.valueOf() === "bigint" ) ) { + s += number( arg.valueOf() ); + continue; + } + if( typeof arg === "string" || arg instanceof String ) { + const objURL = safeURL( arg ); + if( objURL != null && objURL != undefined ) { + let strURL = ""; + const anyURL: any = objURL; + if( anyURL.strStrippedStringComma ) + strURL += normal( anyURL.strStrippedStringComma ); + + if( objURL.protocol ) + strURL += yellow( objURL.protocol ) + normal( "//" ); + + if( objURL.username ) { + strURL += magenta( objURL.username ); + if( objURL.password ) + strURL += normal( ":" ) + yellow( objURL.password ); + + strURL += normal( "@" ); + } + if( objURL.hostname ) + strURL += magenta( logArgToStringAsIpv4( objURL.hostname ) ); + + if( objURL.port ) + strURL += normal( ":" ) + logArgToString( objURL.port ); + + if( objURL.pathname ) + strURL += yellow( replaceAll( objURL.pathname, "/", normal( "/" ) ) ); + + if( objURL.search ) + strURL += magenta( objURL.search ); + + if( anyURL.strStrippedStringComma ) + strURL += normal( anyURL.strStrippedStringComma ); + + s += strURL; + continue; + } + if( ( arg.length > 1 && arg[0] == "-" && arg[1] != "-" ) || + ( arg.length > 2 && arg[0] == "-" && arg[1] == "-" && arg[2] != "-" ) + ) { + s += cla( arg ); + continue; + } + if( arg.length > 0 && ( arg[0] == "\"" || arg[0] == "'" ) ) { + s += strval( arg ); + continue; + } + if( isFloat2( arg ) ) { + s += real( arg ); + continue; + } + if( isInt2( arg ) ) { + s += number( arg ); + continue; + } + } + if( Array.isArray( arg ) || typeof arg === "object" ) { + s += jsonColorizer.prettyPrintConsole( arg ); + continue; + } + s += kk( arg ); + } + return s; +} + +export const getCircularReplacerForJsonStringify = (): any => { + const seen = new WeakSet(); + return ( key: any, value: any ): any => { + if( typeof value === "object" && value !== null ) { + if( seen.has( value ) ) + return; + seen.add( value ); + } + return value; + }; +}; + +export const jsonColorizer: any = { // see http://jsfiddle.net/unLSJ/ + cntCensoredMax: 30000, // zero to disable censoring + censor: ( censor: any ): any => { + let i = 0; + return ( key: any, value: any ) => { + if( i !== 0 && typeof ( censor ) === "object" && + typeof ( value ) === "object" && censor == value + ) + return "[Circular]"; + + if( i >= jsonColorizer.cntCensoredMax ) + return "[Unknown]"; + + ++i; // so we know we aren't using the original object anymore + return value; + }; + }, + replacerHTML: ( match?: any, pIndent?: any, pKey?: any, pVal?: any, pEnd?: any ): any => { + const key = ""; + const val = ""; + const str = ""; + let r = pIndent || ""; + if( pKey ) + r = r + key + pKey.replace( /[": ]/g, "" ) + ": "; + + if( pVal ) + r = r + ( pVal[0] == "\"" ? str : val ) + pVal + ""; + + return r + ( pEnd || "" ); + }, + prettyPrintHTML: ( obj?: any ) => { + const jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; + const s = + JSON.stringify( + obj, ( jsonColorizer.cntCensoredMax > 0 ) + ? jsonColorizer.censor( obj ) + : null, 4 + ) + .replace( /&/g, "&" ).replace( /\\"/g, """ ) + .replace( //g, ">" ) + .replace( jsonLine, jsonColorizer.replacerHTML ); + return s; + }, + replacerConsole: ( match?: any, pIndent?: any, pKey?: any, pVal?: any, pEnd?: any ): any => { + let r = pIndent || ""; + if( pKey ) + r = r + logArgToString( pKey.replace( /[": ]/g, "" ) ) + ": "; + + if( pVal ) + r = r + logArgToString( pVal ); + + return r + ( pEnd || "" ); + }, + prettyPrintConsole: ( obj?: any ): any => { + if( !gFlagIsEnabled ) { + if( obj === null ) + return "null"; + if( obj === undefined ) + return "undefined"; + try { + const s = JSON.stringify( obj ); + return s; + } catch ( err ) { } + try { + const s = JSON.stringify( obj, getCircularReplacerForJsonStringify() ); + return s; + } catch ( err ) { } + try { + const s = obj.toString(); + return s; + } catch ( err ) { } + return obj; + } + const cntSpaces: number = 4; + const jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; + try { + const tmp: string = JSON.stringify( + obj, + ( jsonColorizer.cntCensoredMax > 0 ) ? jsonColorizer.censor( obj ) : null, + cntSpaces + ); + const s = tmp + ? tmp.replace( jsonLine, jsonColorizer.replacerConsole ) + : ( tmp.toString() ); + return s; + } catch ( err ) { } + obj = JSON.parse( JSON.stringify( obj, getCircularReplacerForJsonStringify() ) ); + const tmp = JSON.stringify( + obj, + ( jsonColorizer.cntCensoredMax > 0 ) ? jsonColorizer.censor( obj ) : null, + cntSpaces + ); + const s = tmp ? tmp.replace( jsonLine, jsonColorizer.replacerConsole ) : ( tmp.toString() ); + return s; + } +}; + +// see: +// http://jsfiddle.net/KJQ9K/554 +// https://qastack.ru/programming/4810841/pretty-print-json-using-javascript +export function syntaxHighlightJSON( jo?: any, strKeyNamePrefix?: string ): string { + strKeyNamePrefix = strKeyNamePrefix ?? ""; + jo = jo.replace( /&/g, "&" ).replace( //g, ">" ); + return jo.replace( + // eslint-disable-next-line max-len + /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, + function( match: any ): string { + if( !gFlagIsEnabled ) + return match; + let cls = "number"; + if( /^"/.test( match ) ) { + if( /:$/.test( match ) ) + cls = "key"; + else + cls = "string"; + } else if( /true|false/.test( match ) ) + cls = "boolean"; + else if( /null/.test( match ) ) + cls = "null"; + else if( /NaN/.test( match ) ) + cls = "nan"; + else if( /undefined/.test( match ) ) + cls = "undefined"; + else if( ( typeof match === "string" || match instanceof String ) && + match.length >= 2 && + ( ( match[0] == "\"" && match[match.length - 1] == "\"" ) || + ( match[0] == "'" && match[match.length - 1] == "'" ) ) + ) + cls = "string"; + switch ( cls ) { + case "key": return strKeyNamePrefix + + logArgToString( match.replace( /[": ]/g, "" ) ) + ": "; + case "boolean": + return tf( match ); + case "null": + return nullval( match ); + case "undefined": + return undefval( match ); + case "nan": + return nanval( match ); + case "string": + return strval( match ); + case "number": + return number( match ); + } + return logArgToString( match ); + } ); +} + +export function safeStringifyJSON( jo?: any, n?: number ): string | undefined { + try { + const s = JSON.stringify( jo, getCircularReplacerForJsonStringify(), n ); + return s; + } catch ( err ) { + } + return undefined; +} + +export function jn( x?: any ): string { + return jsonColorizer.prettyPrintConsole( x ); +} + +export function j1( x?: any, n?: number, strKeyNamePrefix?: string ): string { + let isDefaultKeyNamePrefix = false; + if( typeof strKeyNamePrefix !== "string" ) { + strKeyNamePrefix = " "; + isDefaultKeyNamePrefix = true; + } + let s = safeStringifyJSON( x, n ); + if( !gFlagIsEnabled ) + return s ?? ""; + s = syntaxHighlightJSON( s, strKeyNamePrefix ); + if( isDefaultKeyNamePrefix && s.length > 9 && s[0] == " " ) + s = s.substring( 1, s.length ); + return s; +} + +export function j( x?: any ): string { + return j1( x ); // jn +} + +const reset = gMapColorDefinitions.reset; +const enlight = gMapColorDefinitions.enlight; +const dim = gMapColorDefinitions.dim; +const underscore = gMapColorDefinitions.underscore; +const blink = gMapColorDefinitions.blink; +const reverse = gMapColorDefinitions.reverse; +const hidden = gMapColorDefinitions.hidden; +const fgBlack = gMapColorDefinitions.fgBlack; +const fgRed = gMapColorDefinitions.fgRed; +const fgGreen = gMapColorDefinitions.fgGreen; +const fgYellow = gMapColorDefinitions.fgYellow; +const fgBlue = gMapColorDefinitions.fgBlue; +const fgMagenta = gMapColorDefinitions.fgMagenta; +const fgCyan = gMapColorDefinitions.fgCyan; +const fgWhite = gMapColorDefinitions.fgWhite; +const bgBlack = gMapColorDefinitions.bgBlack; +const bgRed = gMapColorDefinitions.bgRed; +const bgGreen = gMapColorDefinitions.bgGreen; +const bgYellow = gMapColorDefinitions.bgYellow; +const bgBlue = gMapColorDefinitions.bgBlue; +const bgMagenta = gMapColorDefinitions.bgMagenta; +const bgCyan = gMapColorDefinitions.bgCyan; +const bBgWhite = gMapColorDefinitions.bBgWhite; +export { + reset, + enlight, + dim, + underscore, + blink, + reverse, + hidden, + fgBlack, + fgRed, + fgGreen, + fgYellow, + fgBlue, + fgMagenta, + fgCyan, + fgWhite, + bgBlack, + bgRed, + bgGreen, + bgYellow, + bgBlue, + bgMagenta, + bgCyan, + bBgWhite +}; + +export function normal( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgWhite + s + reset; +} + +export function trace( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgWhite + s + reset; +} + +export function debug( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgWhite + s + reset; +} +export function debugDark( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgBlack + enlight + s + reset; +} + +export function note( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgBlue + s + reset; +} + +export function notice( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgMagenta + s + reset; +} + +export function info( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgBlue + enlight + s + reset; +} + +export function warning( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgYellow + s + reset; +} + +export function warn( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgYellow + s + reset; +} + +export function error( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgRed + s + reset; +} + +export function fatal( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return bgRed + fgYellow + enlight + s + reset; +} + +export function success( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgGreen + enlight + s + reset; +} + +export function attention( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgCyan + s + reset; +} + +export function bright( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgWhite + enlight + s + reset; +} + +export function sunny( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgYellow + enlight + s + reset; +} + +export function rx( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgMagenta + s + reset; +} + +export function rxa( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgMagenta + enlight + s + reset; +} + +export function tx( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgGreen + s + reset; +} + +export function txa( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgGreen + enlight + s + reset; +} + +export function date( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgYellow + s + reset; +} + +export function time( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgMagenta + enlight + s + reset; +} + +export function fracTime( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgMagenta + s + reset; +} + +export function yes( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgGreen + enlight + s + reset; +} + +export function no( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgRed + s + reset; +} + +export function number( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgBlue + enlight + s + reset; +} + +export function real( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgMagenta + s + reset; +} + +export function undefval( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgGreen + enlight + s + reset; +} + +export function nullval( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgGreen + enlight + s + reset; +} + +export function nanval( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgGreen + enlight + s + reset; +} + +export function yellow( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgYellow + s + reset; +} + +export function magenta( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgMagenta + s + reset; +} + +export function cla( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgBlue + dim + s + reset; +} + +export function kk( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgYellow + enlight + s + reset; +} + +export function strval( s?: any ): string { + if( !gFlagIsEnabled ) + return s ? s.toString() : JSON.stringify( s ); + return fgYellow + s + reset; +} + +export function n2s( n: any, sz: number ): string { + let s = n ? n.toString() : ""; + while( s.length < sz ) + s = "0" + s; + return s; +} + +export function timestampHR(): number { + const newDateTime = new Date(); + const ts = Math.floor( ( newDateTime ).getTime() ); + return ts; +} + +export function timestampUnix(): number { + const newDateTime = new Date(); + const ts = Math.floor( ( newDateTime ).getTime() / 1000 ); + return ts; +} + +function trimLeftUnneededTimestampZeros( s?: any ): string { + while( s.length >= 2 ) { + if( s[0] == "0" && s[1] >= "0" && s[1] <= "9" ) + s = s.substring( 1 ); + else + break; + } + return s; +} + +export function getDurationString( tsFrom: number, tsTo: number ): string { + let s = ""; + let n = tsTo - tsFrom; + + const ms = n % 1000; + n = Math.floor( n / 1000 ); + s += "." + n2s( ms, 3 ); + if( n == 0 ) + return "0" + s; + + const secs = n % 60; + n = Math.floor( n / 60 ); + s = n2s( secs, 2 ) + s; + if( n == 0 ) + return trimLeftUnneededTimestampZeros( s ); + s = ":" + s; + + const mins = n % 60; + n = Math.floor( n / 60 ); + s = n2s( mins, 2 ) + s; + if( n == 0 ) + return trimLeftUnneededTimestampZeros( s ); + s = ":" + s; + + const hours = n % 24; + n = Math.floor( n / 24 ); + s = n2s( hours, 2 ) + s; + if( n == 0 ) + return trimLeftUnneededTimestampZeros( s ); + + return ( n ? n.toString() : "" ) + " " + ( ( n > 1 ) ? "days" : "day" ) + "," + s; +} + +export function capitalizeFirstLetter( s?: any ): string { + if( !s ) + return JSON.stringify( s ); + let s2 = s.toString(); + if( !s2 ) + return s.toString(); + s2 = s2.charAt( 0 ).toUpperCase() + s2.slice( 1 ); + return s2; +} + +function errFnDottedName( s?: any ): string { + const arr = s.split( "." ); + const cnt = arr.length; + let i; let s2 = ""; + for( i = 0; i < cnt; ++i ) { + if( i > 0 ) + s2 += bright( "." ); + s2 += sunny( arr[i] ); + } + return s2; +} + +function errFnName( s?: any ): string { + if( s.indexOf( "async " ) == 0 ) + return bright( "async" ) + " " + errFnDottedName( s.substring( 6 ) ); + return errFnDottedName( s ); +} + +function errLocLn( s: string, isWithBraces?: boolean ): string { + let s2 = ""; + s = s.replace( "file://", "" ); + s = s.replace( "node:", "" ); + if( isWithBraces ) + s2 += " " + debug( "(" ); + const arrCodePoint = s.split( ":" ); + if( arrCodePoint.length > 0 ) { + s2 += trace( arrCodePoint[0] ); + for( let j = 1; j < arrCodePoint.length; ++j ) { + s2 += debug( ":" ); + if( j == 1 ) + s2 += info( arrCodePoint[j] ); + else + s2 += attention( arrCodePoint[j] ); + } + } else + s2 += trace( s ); + if( isWithBraces ) + s2 += debug( ")" ); + return s2; +} + +export function stack( err?: any ): string { + if( !err ) + return ""; + if( err && "stack" in err ) { + const st = err.stack; + if( st && typeof st === "string" ) + err = st; + } + try { + const arr = ( typeof err === "string" ) ? err.split( "\n" ) : err; + const cnt = arr.length; + let i; + for( i = 0; i < cnt; ++i ) { + let s = arr[i].replace( /\s+/g, " " ).trim(); + if( s.indexOf( "at " ) == 0 ) { + // stack entry + s = s.substring( 3 ); + let s2 = " " + debug( "-->" ) + " "; + const n = s.indexOf( " (" ); + if( n > 0 ) { + s2 += errFnName( s.substring( 0, n ) ); + s = s.substring( n + 2 ); + if( s[s.length - 1] == ")" ) + s = s.substring( 0, s.length - 1 ); + s2 += errLocLn( s, true ); + } else + s2 += errLocLn( s, false ); + s = s2; + } else { + // probably error description line + const n = s.indexOf( ":" ); + if( n >= 0 ) { + s = error( + s.substring( 0, n ) ) + normal( ":" ) + warning( s.substring( n + 1 ) ); + } else + s = error( s ); + } + arr[i] = s; + } + return arr.join( "\n" ); + } catch ( errCaught ) { + return err.toString(); + } +} diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 00000000..3bdfa11c --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,2818 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file cli.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as path from "path"; +import * as url from "url"; +import * as os from "os"; +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as imaUtils from "./utils.js"; +import * as rpcCall from "./rpcCall.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaTransferErrorHandling from "./imaTransferErrorHandling.js"; +import * as imaOracleOperations from "./imaOracleOperations.js"; +import * as imaTx from "./imaTx.js"; +import * as state from "./state.js"; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const __dirname: string = path.dirname( url.fileURLToPath( import.meta.url ) ); + +const gStrAppName = "IMA AGENT"; +const gStrVersion = + imaUtils.fileLoad( path.join( __dirname, "../VERSION" ), "N/A" ).toString().trim(); + +function att( ...args: any[] ): string { return log.fmtAttention( ...args ); }; + +export function printAbout( isLog?: boolean ): boolean { + isLog = isLog ?? false; + const strMsg = log.fmtTrace( att( gStrAppName ), " version ", log.fmtNotice( gStrVersion ) ); + if( isLog ) + log.information( strMsg ); + else + console.log( strMsg ); + return true; +} + +export function parseCommandLineArgument( s: string ): any { + const joArg = { + name: "", + value: "" + }; + try { + if( !s ) + return joArg; + while( s.length > 0 && s[0] == "-" ) + s = s.substring( 1 ); + const n = s.indexOf( "=" ); + if( n < 0 ) { + joArg.name = s; + return joArg; + } + joArg.name = s.substring( 0, n ); + joArg.value = s.substring( n + 1 ); + } catch ( err ) {} + return joArg; +} + +// check correctness of command line arguments +export function ensureHaveValue( + name: string, value: any, isExitIfEmpty: boolean, + isPrintValue: boolean, fnNameColorizer?: any, fnValueColorizer?: any +): boolean { + isExitIfEmpty = isExitIfEmpty || false; + isPrintValue = isPrintValue || false; + fnNameColorizer = fnNameColorizer || ( ( x: any ) => { + return log.fmtInformation( x ); + } ); + fnValueColorizer = fnValueColorizer || ( ( x: any ) => { + return log.fmtNotice( x ); + } ); + let retVal = true; + value = value ? value.toString() : ""; + if( value.length === 0 ) { + retVal = false; + if( !isPrintValue ) + console.log( log.fmtError( "WARNING:, missing value for ", fnNameColorizer( name ) ) ); + if( isExitIfEmpty ) + process.exit( 126 ); + } + let strDots = "..."; + let n = 50 - name.length; + for( ; n > 0; --n ) + strDots += "."; + if( isPrintValue ) + log.debug( "{sunny}{raw}{}", fnNameColorizer( name ), strDots, fnValueColorizer( value ) ); + return retVal; +} + +export function ensureHaveCredentials( + strFriendlyChainName: string, joAccount: state.TAccount, + isExitIfEmpty: boolean, isPrintValue: boolean +): boolean { + strFriendlyChainName = strFriendlyChainName || ""; + if( !( typeof joAccount === "object" ) ) { + log.error( "ARGUMENTS VALIDATION WARNING: bad account specified for {} chain", + strFriendlyChainName ); + if( isExitIfEmpty ) + process.exit( 126 ); + } + let cntAccountVariantsSpecified = 0; + if( "strTransactionManagerURL" in joAccount && + typeof joAccount.strTransactionManagerURL === "string" && + joAccount.strTransactionManagerURL.length > 0 + ) { + ++cntAccountVariantsSpecified; + ensureHaveValue( + strFriendlyChainName + "/TM/URL", + joAccount.strTransactionManagerURL, isExitIfEmpty, isPrintValue ); + } + if( "strSgxURL" in joAccount && + typeof joAccount.strSgxURL === "string" && + joAccount.strSgxURL.length > 0 + ) { + ++cntAccountVariantsSpecified; + ensureHaveValue( + strFriendlyChainName + "/SGX/URL", + joAccount.strSgxURL, isExitIfEmpty, isPrintValue ); + if( "strPathSslKey" in joAccount && + typeof joAccount.strPathSslKey === "string" && + joAccount.strPathSslKey.length > 0 + ) { + ensureHaveValue( + strFriendlyChainName + "/SGX/SSL/keyPath", + joAccount.strPathSslKey, isExitIfEmpty, isPrintValue ); + } + if( "strPathSslCert" in joAccount && + typeof joAccount.strPathSslCert === "string" && + joAccount.strPathSslCert.length > 0 + ) { + ensureHaveValue( + strFriendlyChainName + "/SGX/SSL/certPath", + joAccount.strPathSslCert, isExitIfEmpty, isPrintValue ); + } + } + if( "strSgxKeyName" in joAccount && + typeof joAccount.strSgxKeyName === "string" && + joAccount.strSgxKeyName.length > 0 + ) { + ++cntAccountVariantsSpecified; + ensureHaveValue( + strFriendlyChainName + "/SGX/keyName", + joAccount.strSgxKeyName, isExitIfEmpty, isPrintValue ); + } + if( "privateKey" in joAccount && + typeof joAccount.privateKey === "string" && + joAccount.privateKey.length > 0 + ) { + ++cntAccountVariantsSpecified; + ensureHaveValue( + strFriendlyChainName + "/privateKey", + joAccount.privateKey, isExitIfEmpty, isPrintValue ); + } + if( "address_" in joAccount && + typeof joAccount.address_ === "string" && joAccount.address_.length > 0 ) { + ++cntAccountVariantsSpecified; + ensureHaveValue( + strFriendlyChainName + "/walletAddress", + joAccount.address_, isExitIfEmpty, isPrintValue ); + } + if( cntAccountVariantsSpecified == 0 ) { + log.error( "ARGUMENTS VALIDATION WARNING: bad credentials information " + + "specified for {bright} chain, no explicit SGX, no explicit private key, " + + "no wallet address found", strFriendlyChainName ); + if( isExitIfEmpty ) + process.exit( 126 ); + } + return true; +} + +export function findNodeIndex( joSChainNodeConfiguration: any ): number { + try { + const searchID = joSChainNodeConfiguration.skaleConfig.nodeInfo.nodeID; + const cnt = joSChainNodeConfiguration.skaleConfig.sChain.nodes.length; + for( let i = 0; i < cnt; ++i ) { + const joNodeDescription = joSChainNodeConfiguration.skaleConfig.sChain.nodes[i]; + if( joNodeDescription.nodeID == searchID ) + return i; + } + } catch ( err ) {} + return 0; +} + +function parseHelp( imaState: state.TIMAState, joArg: any ): boolean { // exits process on "--help" + if( joArg.name != "help" ) + return false; + printAbout(); + const strAboutText = + imaUtils.fileLoad( path.join( __dirname, "../about.txt" ), "N/A" ) + .toString(); + console.log( strAboutText ); + process.exit( 0 ); +} + +function parseVersion( imaState: state.TIMAState, joArg: any ): boolean { + // exits process on "--version" + if( joArg.name != "version" ) + return false; + printAbout(); + process.exit( 0 ); +} + +function parseBasicArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "colors" ) { + log.enableColorization( true ); + return true; + } + if( joArg.name == "no-colors" ) { + log.enableColorization( false ); + return true; + } + if( joArg.name == "expose" ) { + log.exposeDetailsSet( true ); + return true; + } + if( joArg.name == "no-expose" ) { + log.exposeDetailsSet( false ); + return true; + } + if( joArg.name == "verbose" ) { + log.verboseSet( log.verboseParse( joArg.value ) ); + return true; + } + if( joArg.name == "verbose-list" ) { + log.verboseList(); + return true; + } + return false; +} + +function parseChainAccessArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "url-main-net" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + imaState.chainProperties.mn.strURL = joArg.value; + return true; + } + if( joArg.name == "url-s-chain" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + imaState.chainProperties.sc.strURL = joArg.value; + return true; + } + if( joArg.name == "url-t-chain" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + imaState.chainProperties.tc.strURL = joArg.value; + return true; + } + if( joArg.name == "id-main-net" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.mn.strChainName = joArg.value; + return true; + } + if( joArg.name == "id-s-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.sc.strChainName = joArg.value; + return true; + } + if( joArg.name == "id-origin-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.strChainNameOriginChain = joArg.value; + return true; + } + if( joArg.name == "id-t-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.tc.strChainName = joArg.value; + return true; + } + if( joArg.name == "cid-main-net" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.chainProperties.mn.chainId = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "cid-s-chain" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.chainProperties.sc.chainId = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "cid-t-chain" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.chainProperties.tc.chainId = owaspUtils.toInteger( joArg.value ); + return true; + } + return false; +} + +function parseTransactionManagerArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "tm-url-main-net" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + const strURL = joArg.value.toString(); + imaState.chainProperties.mn.joAccount.strTransactionManagerURL = strURL; + return true; + } + if( joArg.name == "tm-url-s-chain" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + const strURL = joArg.value.toString(); + imaState.chainProperties.sc.joAccount.strTransactionManagerURL = strURL; + return true; + } + if( joArg.name == "tm-url-t-chain" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + const strURL = joArg.value.toString(); + imaState.chainProperties.tc.joAccount.strTransactionManagerURL = strURL; + return true; + } + if( joArg.name == "tm-priority-main-net" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.chainProperties.mn.joAccount.nTmPriority = + owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "tm-priority-s-chain" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.chainProperties.sc.joAccount.nTmPriority = + owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "tm-priority-t-chain" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.chainProperties.tc.joAccount.nTmPriority = + owaspUtils.toInteger( joArg.value ); + return true; + } + return false; +} + +function parseSgxArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "sgx-url-main-net" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + imaState.chainProperties.mn.joAccount.strSgxURL = joArg.value; + return true; + } + if( joArg.name == "sgx-url-s-chain" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + imaState.chainProperties.sc.joAccount.strSgxURL = joArg.value; + return true; + } + if( joArg.name == "sgx-url-t-chain" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + imaState.chainProperties.tc.joAccount.strSgxURL = joArg.value; + return true; + } + if( joArg.name == "sgx-url" ) { + owaspUtils.verifyArgumentIsURL( joArg ); + imaState.chainProperties.mn.joAccount.strSgxURL = + imaState.chainProperties.sc.joAccount.strSgxURL = + imaState.chainProperties.tc.joAccount.strSgxURL = + joArg.value; + return true; + } + if( joArg.name == "sgx-ecdsa-key-main-net" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.mn.joAccount.strSgxKeyName = joArg.value; + return true; + } + if( joArg.name == "sgx-ecdsa-key-s-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.sc.joAccount.strSgxKeyName = joArg.value; + return true; + } + if( joArg.name == "sgx-ecdsa-key-t-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.tc.joAccount.strSgxKeyName = joArg.value; + return true; + } + if( joArg.name == "sgx-ecdsa-key" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.mn.joAccount.strSgxKeyName = + imaState.chainProperties.sc.joAccount.strSgxKeyName = + imaState.chainProperties.tc.joAccount.strSgxKeyName = + joArg.value; + return true; + } + if( joArg.name == "sgx-bls-key-main-net" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.mn.joAccount.strBlsKeyName = joArg.value; + return true; + } + if( joArg.name == "sgx-bls-key-s-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.sc.joAccount.strBlsKeyName = joArg.value; + return true; + } + if( joArg.name == "sgx-bls-key-t-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.tc.joAccount.strBlsKeyName = joArg.value; + return true; + } + if( joArg.name == "sgx-bls-key" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.mn.joAccount.strBlsKeyName = + imaState.chainProperties.sc.joAccount.strBlsKeyName = + imaState.chainProperties.tc.joAccount.strBlsKeyName = + joArg.value; + return true; + } + if( joArg.name == "sgx-ssl-key-main-net" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.mn.joAccount.strPathSslKey = + imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "sgx-ssl-key-s-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.sc.joAccount.strPathSslKey = + imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "sgx-ssl-key-t-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.tc.joAccount.strPathSslKey = + imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "sgx-ssl-key" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.mn.joAccount.strPathSslKey = + imaState.chainProperties.sc.joAccount.strPathSslKey = + imaState.chainProperties.tc.joAccount.strPathSslKey = + imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "sgx-ssl-cert-main-net" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.mn.joAccount.strPathSslCert = + imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "sgx-ssl-cert-s-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.sc.joAccount.strPathSslCert = + imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "sgx-ssl-cert-t-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.tc.joAccount.strPathSslCert = + imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "sgx-ssl-cert" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.mn.joAccount.strPathSslCert = + imaState.chainProperties.sc.joAccount.strPathSslCert = + imaState.chainProperties.tc.joAccount.strPathSslCert = + imaUtils.normalizePath( joArg.value ); + return true; + } + return false; +} + +function parseCredentialsArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "address-main-net" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.mn.joAccount.address_ = joArg.value; + return true; + } + if( joArg.name == "address-s-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.sc.joAccount.address_ = joArg.value; + return true; + } + if( joArg.name == "address-t-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.tc.joAccount.address_ = joArg.value; + return true; + } + if( joArg.name == "receiver" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.receiver = joArg.value; + return true; + } + if( joArg.name == "key-main-net" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.mn.joAccount.privateKey = joArg.value; + return true; + } + if( joArg.name == "key-s-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.sc.joAccount.privateKey = joArg.value; + return true; + } + if( joArg.name == "key-t-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.chainProperties.tc.joAccount.privateKey = joArg.value; + return true; + } + return false; +} + +function parseAbiArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "abi-skale-manager" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.strPathAbiJsonSkaleManager = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "abi-main-net" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.mn.strPathAbiJson = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "abi-s-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.sc.strPathAbiJson = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "abi-t-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.tc.strPathAbiJson = imaUtils.normalizePath( joArg.value ); + return true; + } + return false; +} + +function parseErcArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "erc20-main-net" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.mn.strPathJsonErc20 = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "erc20-s-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.sc.strPathJsonErc20 = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "addr-erc20-s-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.strAddrErc20Explicit = joArg.value; + return true; + } + if( joArg.name == "erc20-t-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.tc.strPathJsonErc20 = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "addr-erc20-t-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.strAddrErc20ExplicitTarget = joArg.value; + return true; + } + + if( joArg.name == "erc721-main-net" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.mn.strPathJsonErc721 = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "erc721-s-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.sc.strPathJsonErc721 = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "addr-erc721-s-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.strAddrErc721Explicit = joArg.value; + return true; + } + if( joArg.name == "erc721-t-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.tc.strPathJsonErc721 = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "addr-erc721-t-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.strAddrErc721ExplicitTarget = joArg.value; + return true; + } + + if( joArg.name == "erc1155-main-net" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.mn.strPathJsonErc1155 = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "erc1155-s-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.sc.strPathJsonErc1155 = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "addr-erc1155-s-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.strAddrErc1155Explicit = joArg.value; + return true; + } + if( joArg.name == "erc1155-t-chain" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.chainProperties.tc.strPathJsonErc1155 = imaUtils.normalizePath( joArg.value ); + return true; + } + if( joArg.name == "addr-erc1155-t-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.strAddrErc1155ExplicitTarget = joArg.value; + return true; + } + if( joArg.name == "with-metadata" ) { + imaState.isWithMetadata721 = true; + return true; + } + return false; +} + +function parseTransactionArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "sleep-between-tx" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaHelperAPIs.setSleepBetweenTransactionsOnSChainMilliseconds( joArg.value ); + return true; + } + if( joArg.name == "wait-next-block" ) { + imaHelperAPIs.setWaitForNextBlockOnSChain( true ); + return true; + } + if( joArg.name == "gas-price-multiplier-mn" ) { + let gasPriceMultiplier: number = owaspUtils.toFloat( joArg.value ); + if( gasPriceMultiplier < 0.0 ) + gasPriceMultiplier = 0.0; + imaState.chainProperties.mn.transactionCustomizer.gasPriceMultiplier = + gasPriceMultiplier; + return true; + } + if( joArg.name == "gas-price-multiplier-sc" ) { + let gasPriceMultiplier = owaspUtils.toFloat( joArg.value ); + if( gasPriceMultiplier < 0.0 ) + gasPriceMultiplier = 0.0; + imaState.chainProperties.sc.transactionCustomizer.gasPriceMultiplier = + gasPriceMultiplier; + return true; + } + if( joArg.name == "gas-price-multiplier-tc" ) { + let gasPriceMultiplier = owaspUtils.toFloat( joArg.value ); + if( gasPriceMultiplier < 0.0 ) + gasPriceMultiplier = 0.0; + imaState.chainProperties.tc.transactionCustomizer.gasPriceMultiplier = + gasPriceMultiplier; + return true; + } + if( joArg.name == "gas-price-multiplier" ) { + let gasPriceMultiplier = owaspUtils.toFloat( joArg.value ); + if( gasPriceMultiplier < 0.0 ) + gasPriceMultiplier = 0.0; + imaState.chainProperties.mn.transactionCustomizer.gasPriceMultiplier = + imaState.chainProperties.sc.transactionCustomizer.gasPriceMultiplier = + imaState.chainProperties.tc.transactionCustomizer.gasPriceMultiplier = + gasPriceMultiplier; + return true; + } + + if( joArg.name == "gas-multiplier-mn" ) { + let gasMultiplier = owaspUtils.toFloat( joArg.value ); + if( gasMultiplier < 0.0 ) + gasMultiplier = 0.0; + imaState.chainProperties.mn.transactionCustomizer.gasMultiplier = + gasMultiplier; + return true; + } + if( joArg.name == "gas-multiplier-sc" ) { + let gasMultiplier = owaspUtils.toFloat( joArg.value ); + if( gasMultiplier < 0.0 ) + gasMultiplier = 0.0; + imaState.chainProperties.sc.transactionCustomizer.gasMultiplier = + gasMultiplier; + return true; + } + if( joArg.name == "gas-multiplier-tc" ) { + let gasMultiplier = owaspUtils.toFloat( joArg.value ); + if( gasMultiplier < 0.0 ) + gasMultiplier = 0.0; + imaState.chainProperties.tc.transactionCustomizer.gasMultiplier = + gasMultiplier; + return true; + } + if( joArg.name == "gas-multiplier" ) { + let gasMultiplier = owaspUtils.toFloat( joArg.value ); + if( gasMultiplier < 0.0 ) + gasMultiplier = 0.0; + imaState.chainProperties.mn.transactionCustomizer.gasMultiplier = + imaState.chainProperties.sc.transactionCustomizer.gasMultiplier = + imaState.chainProperties.tc.transactionCustomizer.gasMultiplier = + gasMultiplier; + return true; + } + if( joArg.name == "skip-dry-run" ) { + imaTx.dryRunEnable( false ); + return true; + } + if( joArg.name == "no-skip-dry-run" ) { + imaTx.dryRunEnable( true ); + return true; + } + if( joArg.name == "ignore-dry-run" ) { + imaTx.dryRunIgnore( true ); + return true; + } + if( joArg.name == "dry-run" || joArg.name == "no-ignore-dry-run" ) { + imaTx.dryRunIgnore( false ); + return true; + } + return false; +} + +function parsePaymentAmountArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "value" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nAmountOfWei = owaspUtils.parseMoneySpecToWei( joArg.value.toString(), true ); + return true; + } + if( joArg.name == "wei" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nAmountOfWei = + owaspUtils.parseMoneySpecToWei( joArg.value.toString() + "wei", true ); + return true; + } + if( joArg.name == "babbage" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nAmountOfWei = + owaspUtils.parseMoneySpecToWei( joArg.value.toString() + "babbage", true ); + return true; + } + if( joArg.name == "lovelace" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nAmountOfWei = + owaspUtils.parseMoneySpecToWei( joArg.value.toString() + "lovelace", true ); + return true; + } + if( joArg.name == "shannon" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nAmountOfWei = + owaspUtils.parseMoneySpecToWei( joArg.value.toString() + "shannon", true ); + return true; + } + if( joArg.name == "szabo" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nAmountOfWei = + owaspUtils.parseMoneySpecToWei( joArg.value.toString() + "szabo", true ); + return true; + } + if( joArg.name == "finney" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nAmountOfWei = + owaspUtils.parseMoneySpecToWei( joArg.value.toString() + "finney", true ); + return true; + } + if( joArg.name == "ether" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nAmountOfWei = + owaspUtils.parseMoneySpecToWei( joArg.value.toString() + "ether", true ); + return true; + } + if( joArg.name == "amount" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nAmountOfToken = joArg.value; + return true; + } + if( joArg.name == "tid" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.idToken = joArg.value; + imaState.haveOneTokenIdentifier = true; + return true; + } + if( joArg.name == "amounts" ) { + imaState.arrAmountsOfTokens = owaspUtils.verifyArgumentIsArrayOfIntegers( joArg ); + return true; + } + if( joArg.name == "tids" ) { + imaState.idTokens = owaspUtils.verifyArgumentIsArrayOfIntegers( joArg ); + imaState.haveArrayOfTokenIdentifiers = true; + return true; + } + return false; +} + +function parseTransferArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "s2s-forward" ) { + imaHelperAPIs.setForwardS2S(); + return true; + } + if( joArg.name == "s2s-reverse" ) { + imaHelperAPIs.setReverseS2S(); + return true; + } + if( joArg.name == "s2s-enable" ) { + imaState.optsS2S.isEnabled = true; + return true; + } + if( joArg.name == "s2s-disable" ) { + imaState.optsS2S.isEnabled = false; + return true; + } + if( joArg.name == "no-wait-s-chain" ) { + imaState.bNoWaitSChainStarted = true; + return true; + } + if( joArg.name == "max-wait-attempts" ) { + imaState.nMaxWaitSChainAttempts = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "m2s-transfer-block-size" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTransferBlockSizeM2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2m-transfer-block-size" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTransferBlockSizeS2M = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2s-transfer-block-size" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTransferBlockSizeS2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "transfer-block-size" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTransferBlockSizeM2S = + imaState.nTransferBlockSizeS2M = + imaState.nTransferBlockSizeS2S = + owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "m2s-transfer-steps" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTransferStepsM2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2m-transfer-steps" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTransferStepsS2M = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2s-transfer-steps" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTransferStepsS2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "transfer-steps" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTransferStepsM2S = + imaState.nTransferStepsS2M = + imaState.nTransferStepsS2S = + owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "m2s-max-transactions" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nMaxTransactionsM2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2m-max-transactions" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nMaxTransactionsS2M = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2s-max-transactions" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nMaxTransactionsS2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "max-transactions" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nMaxTransactionsM2S = + imaState.nMaxTransactionsS2M = + imaState.nMaxTransactionsS2S = + owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "m2s-await-blocks" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nBlockAwaitDepthM2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2m-await-blocks" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nBlockAwaitDepthS2M = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2s-await-blocks" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nBlockAwaitDepthS2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "await-blocks" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nBlockAwaitDepthM2S = + imaState.nBlockAwaitDepthS2M = + imaState.nBlockAwaitDepthS2S = + owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "m2s-await-time" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nBlockAgeM2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2m-await-time" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nBlockAgeS2M = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "s2s-await-time" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nBlockAgeS2S = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "await-time" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nBlockAgeM2S = + imaState.nBlockAgeS2M = + imaState.nBlockAgeS2S = + owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "period" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nLoopPeriodSeconds = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "node-number" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nNodeNumber = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "nodes-count" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nNodesCount = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "time-framing" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTimeFrameSeconds = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "time-gap" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nNextFrameGap = owaspUtils.toInteger( joArg.value ); + return true; + } + return false; +} + +function parseMulticallArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "enable-multicall" ) { + imaState.isEnabledMultiCall = true; + return true; + } + if( joArg.name == "disable-multicall" ) { + imaState.isEnabledMultiCall = false; + return true; + } + return false; +} + +function parsePendingWorkAnalysisArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "pwa" ) { + imaState.isPWA = true; + return true; + } + if( joArg.name == "no-pwa" ) { + imaState.isPWA = false; + return true; + } + if( joArg.name == "pwa-timeout" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nTimeoutSecondsPWA = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "expose-pwa" ) { + imaState.isPrintPWA = true; + return true; + } + if( joArg.name == "no-expose-pwa" ) { + imaState.isPrintPWA = false; + return true; + } + return false; +} + +function parseLoggingArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "gathered" ) { + imaState.isPrintGathered = true; + return true; + } + if( joArg.name == "no-gathered" ) { + imaState.isPrintGathered = false; + return true; + } + if( joArg.name == "expose-security-info" ) { + imaState.isPrintSecurityValues = true; + return true; + } + if( joArg.name == "no-expose-security-info" ) { + imaState.isPrintSecurityValues = false; + return true; + } + if( joArg.name == "log-size" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nLogMaxSizeBeforeRotation = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "log-files" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nLogMaxFilesCount = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "accumulated-log-in-transfer" ) { + imaState.isDynamicLogInDoTransfer = false; + return true; + } + if( joArg.name == "accumulated-log-in-bls-signer" ) { + imaState.isDynamicLogInBlsSigner = false; + return true; + } + if( joArg.name == "dynamic-log-in-transfer" ) { + imaState.isDynamicLogInDoTransfer = true; + return true; + } + if( joArg.name == "dynamic-log-in-bls-signer" ) { + imaState.isDynamicLogInBlsSigner = true; + return true; + } + if( joArg.name == "log" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.strLogFilePath = joArg.value.toString(); + return true; + } + return false; +} + +function parseBlsArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "sign-messages" ) { + imaState.bSignMessages = true; + return true; + } + if( joArg.name == "bls-glue" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.strPathBlsGlue = joArg.value.toString(); + return true; + } + if( joArg.name == "hash-g1" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.strPathHashG1 = joArg.value.toString(); + return true; + } + if( joArg.name == "bls-verify" ) { + owaspUtils.verifyArgumentIsPathToExistingFile( joArg ); + imaState.strPathBlsVerify = joArg.value.toString(); + return true; + } + return false; +} + +function parseMonitoringArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "monitoring-port" ) { + owaspUtils.verifyArgumentIsIntegerIpPortNumber( joArg ); + imaState.nMonitoringPort = owaspUtils.toInteger( joArg.value ); + return true; + } + if( joArg.name == "monitoring-log" ) { + owaspUtils.verifyArgumentIsIntegerIpPortNumber( joArg ); + imaState.bLogMonitoringServer = true; + return true; + } + return false; +} + +function parseReimbursementArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "reimbursement-chain" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.strReimbursementChain = joArg.value.trim(); + return true; + } + if( joArg.name == "reimbursement-recharge" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nReimbursementRecharge = + owaspUtils.parseMoneySpecToWei( joArg.value.toString(), true ); + return true; + } + if( joArg.name == "reimbursement-withdraw" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nReimbursementWithdraw = + owaspUtils.parseMoneySpecToWei( joArg.value.toString(), true ); + return true; + } + if( joArg.name == "reimbursement-balance" ) { + imaState.isShowReimbursementBalance = true; + return true; + } + if( joArg.name == "reimbursement-estimate" ) { + imaState.isReimbursementEstimate = true; + return true; + } + if( joArg.name == "reimbursement-range" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.nReimbursementRange = owaspUtils.toInteger( joArg.value ); + return true; + } + return false; +} + +function parseOracleArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "enable-oracle" ) { + imaOracleOperations.setEnabledOracle( true ); + return true; + } + if( joArg.name == "disable-oracle" ) { + imaOracleOperations.setEnabledOracle( false ); + return true; + } + return false; +} + +function parseNetworkDiscoveryArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "network-browser-path" ) { + owaspUtils.verifyArgumentWithNonEmptyValue( joArg ); + imaState.optsS2S.strNetworkBrowserPath = joArg.value.toString(); + return true; + } + return false; +} + +function parseBlockScannerArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "bs-step-size" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaHelperAPIs.setBlocksCountInInIterativeStepOfEventsScan( + owaspUtils.toInteger( joArg.value ) ); + return true; + } + if( joArg.name == "bs-max-all-range" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaHelperAPIs.setMaxIterationsInAllRangeEventsScan( owaspUtils.toInteger( joArg.value ) ); + return true; + } + if( joArg.name == "bs-progressive-enable" ) { + imaTransferErrorHandling.setEnabledProgressiveEventsScan( true ); + return true; + } + if( joArg.name == "bs-progressive-disable" ) { + imaTransferErrorHandling.setEnabledProgressiveEventsScan( false ); + return true; + } + return false; +} + +function parseJsonRpcServerArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "json-rpc-port" ) { + owaspUtils.verifyArgumentIsIntegerIpPortNumber( joArg ); + imaState.nJsonRpcPort = owaspUtils.toInteger( joArg.value ); + return true; + } + return false; +} + +function parseCrossImaCommunicationArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "cross-ima" ) { + imaState.isCrossImaBlsMode = true; + return true; + } + if( joArg.name == "no-cross-ima" ) { + imaState.isCrossImaBlsMode = false; + return true; + } + return false; +} + +function parseShowConfigArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "show-config" ) { + imaState.bShowConfigMode = true; + return true; + } + return false; +} + +function parseOtherArgs( imaState: state.TIMAState, joArg: any ): boolean { + if( joArg.name == "auto-exit" ) { + owaspUtils.verifyArgumentIsInteger( joArg ); + imaState.nAutoExitAfterSeconds = owaspUtils.toInteger( joArg.value ); + return true; + } + return false; +} + +export function parse( joExternalHandlers: any, argv?: any[] ): number { + const imaState: state.TIMAState = state.get(); + argv = argv ?? process.argv; + const cntArgs = argv.length; + for( let idxArg = 2; idxArg < cntArgs; ++idxArg ) { + const joArg = parseCommandLineArgument( argv[idxArg] ); + parseHelp( imaState, joArg ); // exits process on "--help" + parseVersion( imaState, joArg ); // exits process on "--version" + if( parseBasicArgs( imaState, joArg ) ) + continue; + if( parseChainAccessArgs( imaState, joArg ) ) + continue; + if( parseTransactionManagerArgs( imaState, joArg ) ) + continue; + if( parseSgxArgs( imaState, joArg ) ) + continue; + if( parseCredentialsArgs( imaState, joArg ) ) + continue; + if( parseAbiArgs( imaState, joArg ) ) + continue; + if( parseErcArgs( imaState, joArg ) ) + continue; + if( parseTransactionArgs( imaState, joArg ) ) + continue; + if( parsePaymentAmountArgs( imaState, joArg ) ) + continue; + if( parseTransferArgs( imaState, joArg ) ) + continue; + if( parseMulticallArgs( imaState, joArg ) ) + continue; + if( parsePendingWorkAnalysisArgs( imaState, joArg ) ) + continue; + if( parseLoggingArgs( imaState, joArg ) ) + continue; + if( parseBlsArgs( imaState, joArg ) ) + continue; + if( parseMonitoringArgs( imaState, joArg ) ) + continue; if( parseBlockScannerArgs( imaState, joArg ) ) + continue; + if( parseReimbursementArgs( imaState, joArg ) ) + continue; + if( parseOracleArgs( imaState, joArg ) ) + continue; + if( parseNetworkDiscoveryArgs( imaState, joArg ) ) + continue; + if( parseBlockScannerArgs( imaState, joArg ) ) + continue; + if( parseJsonRpcServerArgs( imaState, joArg ) ) + continue; + if( parseCrossImaCommunicationArgs( imaState, joArg ) ) + continue; + if( parseShowConfigArgs( imaState, joArg ) ) + continue; + if( parseOtherArgs( imaState, joArg ) ) + continue; + if( joArg.name == "register" || + joArg.name == "register1" || + joArg.name == "check-registration" || + joArg.name == "check-registration1" || + joArg.name == "check-registration2" || + joArg.name == "check-registration3" || + joArg.name == "mint-erc20" || + joArg.name == "mint-erc721" || + joArg.name == "mint-erc1155" || + joArg.name == "burn-erc20" || + joArg.name == "burn-erc721" || + joArg.name == "burn-erc1155" || + joArg.name == "show-balance" || + joArg.name == "m2s-payment" || + joArg.name == "s2m-payment" || + joArg.name == "s2m-receive" || + joArg.name == "s2m-view" || + joArg.name == "s2s-payment" || + joArg.name == "m2s-transfer" || + joArg.name == "s2m-transfer" || + joArg.name == "s2s-transfer" || + joArg.name == "transfer" || + joArg.name == "loop" || + joArg.name == "simple-loop" || + joArg.name == "browse-s-chain" + ) { + joExternalHandlers[joArg.name](); + continue; + } + console.log( log.fmtFatal( "COMMAND LINE PARSER ERROR: unknown command line argument {}", + joArg.name ) ); + return 666; + } + return 0; +} + +async function asyncCheckUrlAtStartup( u: URL | string, name: string ): Promise < boolean > { + const details = log.createMemoryStream(); + const nTimeoutMilliseconds = 10 * 1000; + try { + details.debug( "Will check URL {url} connectivity for {} at start-up...", u, name ); + const isLog = false; + const isOnLine = await rpcCall.checkUrl( u, nTimeoutMilliseconds, isLog ); + if( isOnLine ) { + details.success( "Done, start-up checking URL {url} connectivity for {}, " + + "URL is on-line.", u, name ); + } else { + details.warning( "Done, start-up checking URL {url} connectivity for {}, " + + "URL is off-line.", u, name ); + } + return isOnLine; + } catch ( err ) { + details.error( "Failed to check URL {url} connectivity for {} at start-up, " + + "error is: {err}, stack is:\n{stack}", u, name, err, err ); + } + return false; +} + +function commonInitPrintSysInfo(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + if( isPrintGathered ) { + log.debug( "This process {sunny} is {}", "versions", process.versions ); + log.debug( "This process {sunny} is {}", "PID", process.pid ); + log.debug( "This process {sunny} is {}", "PPID", process.ppid ); + log.debug( "This process {sunny} is {}", "EGID", + process.getegid ? process.getegid() : "N/A" ); + log.debug( "This process {sunny} is {}", "EUID", + process.geteuid ? process.geteuid() : "N/A" ); + log.debug( "This process {sunny} is {}", "GID", + process.getgid ? process.getgid() : "N/A" ); + log.debug( "This process {sunny} is {}", "UID", + process.getuid ? process.getuid() : "N/A" ); + log.debug( "This process {sunny} are {}", "groups", + process.getgroups ? process.getgroups() : "N/A" ); + log.debug( "This process {sunny} is {}", "CWD", process.cwd() ); + log.debug( "This process {sunny} is {}", "architecture", process.arch ); + log.debug( "This process {sunny} is {}", "execPath", process.execPath ); + log.debug( "This process {sunny} is {}", "platform", process.platform ); + log.debug( "This process {sunny} is {}", "release", process.release ); + log.debug( "This process {sunny} is {}", "report", process.report ); + log.debug( "This process {sunny} is {}", "config", process.config ); + log.debug( "Node JS {sunny} is {}", "detailed version information", process.versions ); + log.debug( "OS {sunny} is {}", "type", os.type() ); + log.debug( "OS {sunny} is {}", "platform", os.platform() ); + log.debug( "OS {sunny} is {}", "release", os.release() ); + log.debug( "OS {sunny} is {}", "architecture", os.arch() ); + log.debug( "OS {sunny} is {}", "endianness", os.endianness() ); + log.debug( "OS {sunny} is {}", "host name", os.hostname() ); + log.debug( "OS {sunny} is {}", "CPUs are ", os.cpus() ); + log.debug( "OS {sunny} are {}", "network interfaces", os.networkInterfaces() ); + log.debug( "OS {sunny} is {}", "home dir", os.homedir() ); + log.debug( "OS {sunny} is {}", "tmp dir", os.tmpdir() ); + log.debug( "OS {sunny} is {}", "uptime", os.uptime() ); + log.debug( "OS {sunny} is {}", "user", os.userInfo() ); + const joMemory: any = { total: os.totalmem(), free: os.freemem() }; + joMemory.freePercent = ( joMemory.free / joMemory.total ) * 100.0; + log.debug( "OS {sunny} is {}", "memory", joMemory ); + const joLA = os.loadavg(); + log.debug( "OS {sunny} is {}", "average load", joLA ); + } +} + +function commonInitCheckAbiPaths(): void { + const imaState: state.TIMAState = state.get(); + if( imaState.strPathAbiJsonSkaleManager && + ( typeof imaState.strPathAbiJsonSkaleManager === "string" ) && + imaState.strPathAbiJsonSkaleManager.length > 0 + ) { + imaState.joAbiSkaleManager = + imaUtils.jsonFileLoad( imaState.strPathAbiJsonSkaleManager, null ); + imaState.bHaveSkaleManagerABI = true; + } else { + imaState.bHaveSkaleManagerABI = false; + log.warning( "WARNING: No Skale Manager ABI file path is provided in command line " + + "arguments(needed for particular operations only)" ); + } + + if( imaState.chainProperties.mn.strPathAbiJson && + typeof imaState.chainProperties.mn.strPathAbiJson === "string" && + imaState.chainProperties.mn.strPathAbiJson.length > 0 ) { + imaState.chainProperties.mn.joAbiIMA = + imaUtils.jsonFileLoad( imaState.chainProperties.mn.strPathAbiJson, null ); + imaState.chainProperties.mn.bHaveAbiIMA = true; + } else { + imaState.chainProperties.mn.bHaveAbiIMA = false; + log.warning( "WARNING: No Main-net IMA ABI file path is provided in command line " + + "arguments(needed for particular operations only)" ); + } + + if( imaState.chainProperties.sc.strPathAbiJson && + typeof imaState.chainProperties.sc.strPathAbiJson === "string" && + imaState.chainProperties.sc.strPathAbiJson.length > 0 + ) { + imaState.chainProperties.sc.joAbiIMA = + imaUtils.jsonFileLoad( imaState.chainProperties.sc.strPathAbiJson, null ); + imaState.chainProperties.sc.bHaveAbiIMA = true; + } else { + imaState.chainProperties.sc.bHaveAbiIMA = false; + log.warning( "WARNING: No S-Chain IMA ABI file path is provided in command line arguments" + + "(needed for particular operations only)" ); + } + + if( imaState.chainProperties.tc.strPathAbiJson && + typeof imaState.chainProperties.tc.strPathAbiJson === "string" && + imaState.chainProperties.tc.strPathAbiJson.length > 0 + ) { + imaState.chainProperties.tc.joAbiIMA = + imaUtils.jsonFileLoad( imaState.chainProperties.tc.strPathAbiJson, null ); + imaState.chainProperties.tc.bHaveAbiIMA = true; + } else { + imaState.chainProperties.tc.bHaveAbiIMA = false; + log.warning( "WARNING: No S<->S Target S-Chain IMA ABI file path is provided " + + "in command line arguments(needed for particular operations only)" ); + } +} + +function commonInitCheckContractPresences(): void { + const imaState: state.TIMAState = state.get(); + if( imaState.bHaveSkaleManagerABI ) { + imaUtils.checkKeysExistInABI( "skale-manager", + imaState.strPathAbiJsonSkaleManager, + imaState.joAbiSkaleManager, [ + // partial list of Skale Manager's contracts specified here: + "constants_holder_abi", + "constants_holder_address", + "nodes_abi", + "nodes_address", + "key_storage_abi", + "key_storage_address", + "schains_abi", + "schains_address", + "schains_internal_abi", + "schains_internal_address", + "skale_d_k_g_abi", + "skale_d_k_g_address", + "skale_manager_abi", + "skale_manager_address", + "skale_token_abi", + "skale_token_address", + "validator_service_abi", + "validator_service_address", + "wallets_abi", + "wallets_address" + ] ); + } else if( imaState.optsS2S.isEnabled ) + log.warning( "WARNING: Missing Skale Manager ABI path for S-Chain to S-Chain transfers" ); + + if( imaState.chainProperties.mn.bHaveAbiIMA ) { + imaUtils.checkKeysExistInABI( "main-net", + imaState.chainProperties.mn.strPathAbiJson, + imaState.chainProperties.mn.joAbiIMA, [ + "deposit_box_eth_abi", + "deposit_box_eth_address", + "message_proxy_mainnet_abi", + "message_proxy_mainnet_address", + "linker_abi", + "linker_address", + "deposit_box_erc20_abi", + "deposit_box_erc20_address", + "deposit_box_erc721_abi", + "deposit_box_erc721_address", + "deposit_box_erc1155_abi", + "deposit_box_erc1155_address", + "deposit_box_erc721_with_metadata_abi", + "deposit_box_erc721_with_metadata_address", + "community_pool_abi", + "community_pool_address" + ] ); + } + if( imaState.chainProperties.sc.bHaveAbiIMA ) { + imaUtils.checkKeysExistInABI( "S-Chain", + imaState.chainProperties.sc.strPathAbiJson, + imaState.chainProperties.sc.joAbiIMA, [ + "token_manager_eth_abi", + "token_manager_eth_address", + "token_manager_erc20_abi", + "token_manager_erc20_address", + "token_manager_erc721_abi", + "token_manager_erc721_address", + "token_manager_erc1155_abi", + "token_manager_erc1155_address", + "token_manager_erc721_with_metadata_abi", + "token_manager_erc721_with_metadata_address", + "message_proxy_chain_abi", + "message_proxy_chain_address", + "token_manager_linker_abi", + "token_manager_linker_address", + "community_locker_abi", + "community_locker_address" + ] ); + } + if( imaState.chainProperties.tc.bHaveAbiIMA ) { + imaUtils.checkKeysExistInABI( "S<->S Target S-Chain", + imaState.chainProperties.tc.strPathAbiJson, + imaState.chainProperties.tc.joAbiIMA, [ + "token_manager_eth_abi", + "token_manager_eth_address", + "token_manager_erc20_abi", + "token_manager_erc20_address", + "token_manager_erc721_abi", + "token_manager_erc721_address", + "token_manager_erc1155_abi", + "token_manager_erc1155_address", + "token_manager_erc721_with_metadata_abi", + "token_manager_erc721_with_metadata_address", + "message_proxy_chain_abi", + "message_proxy_chain_address", + "token_manager_linker_abi", + "token_manager_linker_address", + "community_locker_abi", + "community_locker_address" + ] ); + } +} + +function commonInitPrintFoundContracts(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + // deposit_box_eth_address --> deposit_box_eth_abi + // deposit_box_erc20_address --> deposit_box_erc20_abi + // deposit_box_erc721_address --> deposit_box_erc721_abi + // deposit_box_erc1155_address --> deposit_box_erc1155_abi + // deposit_box_erc721_with_metadata_address --> deposit_box_erc721_with_metadata_abi + // linker_address --> linker_abi + // token_manager_eth_address --> token_manager_eth_abi + // token_manager_erc20_address --> token_manager_erc20_abi + // token_manager_erc721_address --> token_manager_erc721_abi + // token_manager_erc1155_address --> token_manager_erc1155_abi + // token_manager_erc721_with_metadata_address --> token_manager_erc721_with_metadata_abi + // token_manager_linker_address --> token_manager_linker_abi + // message_proxy_mainnet_address --> message_proxy_mainnet_abi + // message_proxy_chain_address --> message_proxy_chain_abi + + const oct = function( + joContract?: owaspUtils.ethersMod.ethers.Contract | null ): string { + // optional contract address + if( joContract && "address" in joContract && joContract.address ) + return log.fmtInformation( "{}", joContract.address ); + return log.fmtError( "contract is not available" ); + }; + + if( isPrintGathered ) { + log.debug( "IMA contracts(Main Net):" ); + log.debug( "{sunny}...................address is.....{}", "DepositBoxEth", + oct( imaState.joDepositBoxETH ) ); + log.debug( "{sunny}.................address is.....{}", "DepositBoxERC20", + oct( imaState.joDepositBoxERC20 ) ); + log.debug( "{sunny}................address is.....{}", "DepositBoxERC721", + oct( imaState.joDepositBoxERC721 ) ); + log.debug( "{sunny}...............address is.....{}", "DepositBoxERC1155", + oct( imaState.joDepositBoxERC1155 ) ); + log.debug( "{sunny}....address is.....{}", "DepositBoxERC721WithMetadata", + oct( imaState.joDepositBoxERC721WithMetadata ) ); + log.debug( "{sunny}...................address is.....{}", "CommunityPool", + oct( imaState.joCommunityPool ) ); + log.debug( "{sunny}....................address is.....{}", "MessageProxy", + oct( imaState.joMessageProxyMainNet ) ); + log.debug( "{sunny}..........................address is.....{}", "Linker", + oct( imaState.joLinker ) ); + log.debug( "IMA contracts(S-Chain):" ); + log.debug( "{sunny}.................address is.....{}", "TokenManagerEth", + oct( imaState.joTokenManagerETH ) ); + log.debug( "{sunny}...............address is.....{}", "TokenManagerERC20", + oct( imaState.joTokenManagerERC20 ) ); + log.debug( "{sunny}..............address is.....{}", "TokenManagerERC721", + oct( imaState.joTokenManagerERC721 ) ); + log.debug( "{sunny}.............address is.....{}", "TokenManagerERC1155", + oct( imaState.joTokenManagerERC1155 ) ); + log.debug( "{sunny}..address is.....{}", "TokenManagerERC721WithMetadata", + oct( imaState.joTokenManagerERC721WithMetadata ) ); + log.debug( "{sunny}.................address is.....{}", "CommunityLocker", + oct( imaState.joCommunityLocker ) ); + log.debug( "{sunny}....................address is.....{}", "MessageProxy", + oct( imaState.joMessageProxySChain ) ); + log.debug( "{sunny}..............address is.....{}", "TokenManagerLinker", + oct( imaState.joTokenManagerLinker ) ); + log.debug( "{sunny} ..........................address is.....{}", "ERC20", + oct( imaState.joEthErc20 ) ); + log.debug( "IMA contracts(Target S-Chain):" ); + log.debug( "{sunny}...............address is.....{}", "TokenManagerERC20", + oct( imaState.joTokenManagerERC20Target ) ); + log.debug( "{sunny}..............address is.....{}", "TokenManagerERC721", + oct( imaState.joTokenManagerERC721Target ) ); + log.debug( "{sunny}.............address is.....{}", "TokenManagerERC1155", + oct( imaState.joTokenManagerERC1155Target ) ); + log.debug( "{sunny}..address is.....{}", "TokenManagerERC721WithMetadata", + oct( imaState.joTokenManagerERC721WithMetadataTarget ) ); + log.debug( "{sunny}.................address is.....{}", "CommunityLocker", + oct( imaState.joCommunityLockerTarget ) ); + log.debug( "{sunny}....................address is.....{}", "MessageProxy", + oct( imaState.joMessageProxySChainTarget ) ); + log.debug( "{sunny}..............address is.....{}", "TokenManagerLinker", + oct( imaState.joTokenManagerLinkerTarget ) ); + log.debug( "{sunny} ..........................address is.....{}", "ERC20", + oct( imaState.joEthErc20Target ) ); + + log.debug( "Skale Manager contracts:" ); + log.debug( "{sunny}.................address is.....{}", "ConstantsHolder", + oct( imaState.joConstantsHolder ) ); + log.debug( "{sunny}...........................address is.....{}", "Nodes", + oct( imaState.joNodes ) ); + log.debug( "{sunny}......................address is.....{}", "KeyStorage", + oct( imaState.joKeyStorage ) ); + log.debug( "{sunny}.........................address is.....{}", "Schains", + oct( imaState.joSChains ) ); + log.debug( "{sunny}.................address is.....{}", "SchainsInternal", + oct( imaState.joSChainsInternal ) ); + log.debug( "{sunny}........................address is.....{}", "SkaleDKG", + oct( imaState.joSkaleDKG ) ); + log.debug( "{sunny}....................address is.....{}", "SkaleManager", + oct( imaState.joSkaleManager ) ); + log.debug( "{sunny}......................address is.....{}", "SkaleToken", + oct( imaState.joSkaleToken ) ); + log.debug( "{sunny}................address is.....{}", "ValidatorService", + oct( imaState.joValidatorService ) ); + log.debug( "{sunny}.........................address is.....{}", "Wallets", + oct( imaState.joWallets ) ); + } +} + +function commonInitCheckErc20(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + let n1 = 0; + let n2 = 0; + if( imaState.chainProperties.mn.strPathJsonErc20.length > 0 ) { + if( isPrintGathered ) { + log.information( "Loading Main-net ERC20 ABI from {}", + imaState.chainProperties.mn.strPathJsonErc20 ); + } + imaState.chainProperties.mn.joErc20 = + imaUtils.jsonFileLoad( imaState.chainProperties.mn.strPathJsonErc20, null ); + n1 = Object.keys( imaState.chainProperties.mn.joErc20 ).length; + if( imaState.chainProperties.sc.strPathJsonErc20.length > 0 ) { + if( isPrintGathered ) { + log.information( "Loading S-Chain ERC20 ABI from {}", + imaState.chainProperties.sc.strPathJsonErc20 ); + } + imaState.chainProperties.sc.joErc20 = + imaUtils.jsonFileLoad( imaState.chainProperties.sc.strPathJsonErc20, null ); + n2 = Object.keys( imaState.chainProperties.sc.joErc20 ).length; + } + if( n1 > 0 ) { + imaState.chainProperties.tc.strCoinNameErc20 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.mn.joErc20 ); + if( n2 > 0 ) { + imaState.chainProperties.sc.strCoinNameErc20 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.sc.joErc20 ); + } + n1 = imaState.chainProperties.tc.strCoinNameErc20.length; + if( n2 > 0 ) + n2 = imaState.chainProperties.sc.strCoinNameErc20.length; + if( n1 > 0 ) { + if( isPrintGathered && + ( !imaState.bShowConfigMode ) + ) { + if( isPrintGathered ) { + log.information( "Loaded Main-net ERC20 ABI {}", + imaState.chainProperties.tc.strCoinNameErc20 ); + } + if( isPrintGathered && n2 > 0 ) { + log.information( "Loaded S-Chain ERC20 ABI {}", + imaState.chainProperties.sc.strCoinNameErc20 ); + } + } + } else { + if( n1 === 0 ) + log.error( "Main-net ERC20 token name is not discovered(malformed JSON)" ); + + if( n2 === 0 && imaState.chainProperties.sc.strPathJsonErc20.length > 0 ) + log.error( "S-Chain ERC20 token name is not discovered(malformed JSON)" ); + + imaState.chainProperties.mn.joErc20 = null; + imaState.chainProperties.sc.joErc20 = null; + imaState.chainProperties.tc.strCoinNameErc20 = ""; + imaState.chainProperties.sc.strCoinNameErc20 = ""; + process.exit( 126 ); + } + } else { + if( n1 === 0 ) + log.error( "Main-net ERC20 JSON is invalid" ); + if( n2 === 0 && imaState.chainProperties.sc.strPathJsonErc20.length > 0 ) + log.error( "S-Chain ERC20 JSON is invalid" ); + imaState.chainProperties.mn.joErc20 = null; + imaState.chainProperties.sc.joErc20 = null; + imaState.chainProperties.tc.strCoinNameErc20 = ""; + imaState.chainProperties.sc.strCoinNameErc20 = ""; + process.exit( 126 ); + } + } else { + if( imaState.chainProperties.sc.strPathJsonErc20.length > 0 ) { + n1 = 0; + n2 = 0; + if( isPrintGathered ) { + log.information( "Loading S-Chain ERC20 ABI from {}", + imaState.chainProperties.sc.strPathJsonErc20 ); + } + imaState.chainProperties.sc.joErc20 = + imaUtils.jsonFileLoad( imaState.chainProperties.sc.strPathJsonErc20, null ); + n2 = Object.keys( imaState.chainProperties.sc.joErc20 ).length; + if( n2 > 0 ) { + imaState.chainProperties.sc.strCoinNameErc20 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.sc.joErc20 ); + n2 = imaState.chainProperties.sc.strCoinNameErc20.length; + if( n2 > 0 ) { + if( isPrintGathered ) { + log.information( "Loaded S-Chain ERC20 ABI {}", + imaState.chainProperties.sc.strCoinNameErc20 ); + } + } else { + if( n2 === 0 && imaState.chainProperties.sc.strPathJsonErc20.length > 0 ) + log.error( "S-Chain ERC20 token name is not discovered(malformed JSON)" ); + imaState.chainProperties.mn.joErc20 = null; + imaState.chainProperties.sc.joErc20 = null; + imaState.chainProperties.tc.strCoinNameErc20 = ""; + imaState.chainProperties.sc.strCoinNameErc20 = ""; + process.exit( 126 ); + } + } + } + } + if( n1 !== 0 && n2 === 0 ) { + if( imaState.strAddrErc20Explicit.length === 0 ) { + log.warning( "IMPORTANT NOTICE: Both S-Chain ERC20 JSON and explicit " + + "ERC20 address are not specified" ); + } else { + if( isPrintGathered ) + log.attention( "IMPORTANT NOTICE: S-Chain ERC20 ABI will be auto-generated" ); + imaState.chainProperties.sc.strCoinNameErc20 = + imaState.chainProperties.tc.strCoinNameErc20 ?? ""; // assume same + imaState.chainProperties.sc.joErc20 = + JSON.parse( JSON.stringify( imaState.chainProperties.mn.joErc20 ) ); // clone + imaState.chainProperties.sc.joErc20[ + imaState.chainProperties.sc.strCoinNameErc20 + "_address"] = + imaState.strAddrErc20Explicit; // set explicit address + } + } + + if( imaState.chainProperties.tc.strPathJsonErc20.length > 0 ) { + if( isPrintGathered ) { + log.information( "Loading S<->S Target S-Chain ERC20 ABI from {}", + imaState.chainProperties.tc.strPathJsonErc20 ); + } + imaState.chainProperties.tc.joErc20 = + imaUtils.jsonFileLoad( imaState.chainProperties.tc.strPathJsonErc20, null ); + n2 = Object.keys( imaState.chainProperties.tc.joErc20 ).length; + if( n2 > 0 ) { + imaState.chainProperties.tc.strCoinNameErc20 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.tc.joErc20 ); + n2 = imaState.chainProperties.tc.strCoinNameErc20.length; + if( n2 > 0 ) { + if( isPrintGathered ) { + log.information( "Loaded S<->S Target S-Chain ERC20 ABI {}", + imaState.chainProperties.tc.strCoinNameErc20 ); + } + } else { + if( n2 === 0 && imaState.chainProperties.tc.strPathJsonErc20.length > 0 ) { + log.fatal( "S<->S Target S-Chain ERC20 token name " + + "is not discovered(malformed JSON)" ); + } + imaState.chainProperties.tc.joErc20 = null; + imaState.chainProperties.tc.strCoinNameErc20 = ""; + process.exit( 126 ); + } + } + } + if( isPrintGathered && + imaState.strAddrErc20ExplicitTarget.length === 0 && + imaState.chainProperties.tc.strCoinNameErc20.length === 0 && + imaState.chainProperties.sc.strCoinNameErc20.length > 0 + ) { + log.warning( "IMPORTANT NOTICE: Both S<->S Target S-Chain ERC20 JSON and explicit " + + "ERC20 address are not specified" ); + } +} + +function commonInitCheckErc721(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + let n1 = 0; + let n2 = 0; + if( imaState.chainProperties.mn.strPathJsonErc721.length > 0 ) { + if( isPrintGathered ) { + log.information( "Loading Main-net ERC721 ABI from {}", + imaState.chainProperties.mn.strPathJsonErc721 ); + } + imaState.chainProperties.mn.joErc721 = + imaUtils.jsonFileLoad( imaState.chainProperties.mn.strPathJsonErc721, null ); + n1 = Object.keys( imaState.chainProperties.mn.joErc721 ).length; + if( imaState.chainProperties.sc.strPathJsonErc721.length > 0 ) { + if( isPrintGathered ) { + log.information( "Loading S-Chain ERC721 ABI from {}", + imaState.chainProperties.sc.strPathJsonErc721 ); + } + imaState.chainProperties.sc.joErc721 = + imaUtils.jsonFileLoad( imaState.chainProperties.sc.strPathJsonErc721, null ); + n2 = Object.keys( imaState.chainProperties.sc.joErc721 ).length; + } + if( n1 > 0 ) { + imaState.chainProperties.mn.strCoinNameErc721 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.mn.joErc721 ); + if( n2 > 0 ) { + imaState.chainProperties.sc.strCoinNameErc721 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.sc.joErc721 ); + } + n1 = imaState.chainProperties.mn.strCoinNameErc721.length; + if( n2 > 0 ) + n2 = imaState.chainProperties.sc.strCoinNameErc721.length; + if( n1 > 0 ) { + if( !imaState.bShowConfigMode ) { + if( isPrintGathered ) { + log.information( "Loaded Main-net ERC721 ABI {}", + imaState.chainProperties.mn.strCoinNameErc721 ); + } + if( n2 > 0 && isPrintGathered ) { + log.information( "Loaded S-Chain ERC721 ABI {}", + imaState.chainProperties.sc.strCoinNameErc721 ); + } + } + } else { + if( n1 === 0 ) + log.fatal( "Main-net ERC721 token name is not discovered(malformed JSON)" ); + if( n2 === 0 && imaState.chainProperties.sc.strPathJsonErc721.length > 0 ) + log.fatal( "S-Chain ERC721 token name is not discovered(malformed JSON)" ); + imaState.chainProperties.mn.joErc721 = null; + imaState.chainProperties.sc.joErc721 = null; + imaState.chainProperties.mn.strCoinNameErc721 = ""; + imaState.chainProperties.sc.strCoinNameErc721 = ""; + process.exit( 126 ); + } + } else { + if( n1 === 0 ) + log.fatal( "Main-net ERC721 JSON is invalid" ); + if( n2 === 0 && imaState.chainProperties.sc.strPathJsonErc721.length > 0 ) + log.fatal( "S-Chain ERC721 JSON is invalid" ); + imaState.chainProperties.mn.joErc721 = null; + imaState.chainProperties.sc.joErc721 = null; + imaState.chainProperties.mn.strCoinNameErc721 = ""; + imaState.chainProperties.sc.strCoinNameErc721 = ""; + process.exit( 126 ); + } + } else { + if( imaState.chainProperties.sc.strPathJsonErc721.length > 0 ) { + n1 = 0; + n2 = 0; + if( isPrintGathered ) { + log.information( "Loading S-Chain ERC721 ABI from {}", + imaState.chainProperties.sc.strPathJsonErc721 ); + } + imaState.chainProperties.sc.joErc721 = + imaUtils.jsonFileLoad( imaState.chainProperties.sc.strPathJsonErc721, null ); + n2 = Object.keys( imaState.chainProperties.sc.joErc721 ).length; + + if( n2 > 0 ) { + imaState.chainProperties.sc.strCoinNameErc721 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.sc.joErc721 ); + n2 = imaState.chainProperties.sc.strCoinNameErc721.length; + if( n2 > 0 ) { + if( isPrintGathered ) { + log.information( "Loaded S-Chain ERC721 ABI {}", + imaState.chainProperties.sc.strCoinNameErc721 ); + } else { + if( n2 === 0 && imaState.chainProperties.sc.strPathJsonErc721.length > 0 ) { + log.fatal( "S-Chain ERC721 token name is not " + + "discovered(malformed JSON)" ); + } + imaState.chainProperties.mn.joErc721 = null; + imaState.chainProperties.sc.joErc721 = null; + imaState.chainProperties.mn.strCoinNameErc721 = ""; + imaState.chainProperties.sc.strCoinNameErc721 = ""; + process.exit( 126 ); + } + } + } + } + } + if( n1 !== 0 && n2 === 0 ) { + if( imaState.strAddrErc721Explicit.length === 0 ) { + if( isPrintGathered ) { + log.warning( "IMPORTANT NOTICE: Both S-Chain ERC721 JSON and explicit " + + "ERC721 address are not specified" ); + } + } else { + if( isPrintGathered ) + log.attention( "IMPORTANT NOTICE: S-Chain ERC721 ABI will be auto-generated" ); + imaState.chainProperties.sc.strCoinNameErc721 = + imaState.chainProperties.mn.strCoinNameErc721 ?? ""; // assume same + imaState.chainProperties.sc.joErc721 = + JSON.parse( JSON.stringify( imaState.chainProperties.mn.joErc721 ) ); // clone + imaState.chainProperties.sc.joErc721[ + imaState.chainProperties.sc.strCoinNameErc721 + "_address"] = + imaState.strAddrErc721Explicit; // set explicit address + } + } + + if( imaState.chainProperties.tc.strPathJsonErc721.length > 0 && + isPrintGathered + ) { + log.information( "Loading S<->S Target S-Chain ERC721 ABI from {}", + imaState.chainProperties.tc.strPathJsonErc721 ); + imaState.chainProperties.tc.joErc721 = + imaUtils.jsonFileLoad( imaState.chainProperties.tc.strPathJsonErc721, null ); + n2 = Object.keys( imaState.chainProperties.tc.joErc721 ).length; + if( n2 > 0 ) { + imaState.chainProperties.tc.strCoinNameErc721 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.tc.joErc721 ); + n2 = imaState.chainProperties.tc.strCoinNameErc721.length; + if( n2 > 0 && isPrintGathered ) { + log.information( "Loaded S<->S Target S-Chain ERC721 ABI {}", + imaState.chainProperties.tc.strCoinNameErc721 ); + } else { + if( n2 === 0 && + imaState.chainProperties.tc.strPathJsonErc721.length > 0 && + isPrintGathered + ) { + log.fatal( "S<->S Target S-Chain ERC721 token name " + + "is not discovered(malformed JSON)" ); + } + imaState.chainProperties.tc.joErc721 = null; + imaState.chainProperties.tc.strCoinNameErc721 = ""; + process.exit( 126 ); + } + } + } + if( isPrintGathered && + imaState.strAddrErc721ExplicitTarget.length === 0 && + imaState.chainProperties.tc.strCoinNameErc721.length === 0 && + imaState.chainProperties.sc.strCoinNameErc721.length > 0 + ) { + log.warning( "IMPORTANT NOTICE: Both S<->S Target S-Chain ERC721 JSON and " + + "explicit ERC721 address are not specified" ); + } +} + +function commonInitCheckErc1155(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + let n1 = 0; + let n2 = 0; + if( imaState.chainProperties.mn.strPathJsonErc1155.length > 0 ) { + if( isPrintGathered ) { + log.information( "Loading Main-net ERC1155 ABI from {}", + imaState.chainProperties.mn.strPathJsonErc1155 ); + } + imaState.chainProperties.mn.joErc1155 = + imaUtils.jsonFileLoad( imaState.chainProperties.mn.strPathJsonErc1155, null ); + n1 = Object.keys( imaState.chainProperties.mn.joErc1155 ).length; + if( imaState.chainProperties.sc.strPathJsonErc1155.length > 0 ) { + if( isPrintGathered ) { + log.information( "Loading S-Chain ERC1155 ABI from {}", + imaState.chainProperties.sc.strPathJsonErc1155 ); + } + imaState.chainProperties.sc.joErc1155 = + imaUtils.jsonFileLoad( imaState.chainProperties.sc.strPathJsonErc1155, null ); + n2 = Object.keys( imaState.chainProperties.sc.joErc1155 ).length; + } + if( n1 > 0 ) { + imaState.chainProperties.mn.strCoinNameErc1155 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.mn.joErc1155 ); + if( n2 > 0 ) { + imaState.chainProperties.sc.strCoinNameErc1155 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.sc.joErc1155 ); + } + n1 = imaState.chainProperties.mn.strCoinNameErc1155.length; + if( n2 > 0 ) + n2 = imaState.chainProperties.sc.strCoinNameErc1155.length; + if( n1 > 0 ) { + if( !imaState.bShowConfigMode ) { + if( isPrintGathered ) { + log.information( "Loaded Main-net ERC1155 ABI {}", + imaState.chainProperties.mn.strCoinNameErc1155 ); + } + if( n2 > 0 && isPrintGathered ) { + log.information( "Loaded S-Chain ERC1155 ABI {}", + imaState.chainProperties.sc.strCoinNameErc1155 ); + } + } + } else { + if( n1 === 0 ) + log.fatal( "Main-net ERC1155 token name is not discovered(malformed JSON)" ); + if( n2 === 0 && imaState.chainProperties.sc.strPathJsonErc1155.length > 0 ) + log.fatal( "S-Chain ERC1155 token name is not discovered(malformed JSON)" ); + imaState.chainProperties.mn.joErc1155 = null; + imaState.chainProperties.sc.joErc1155 = null; + imaState.chainProperties.mn.strCoinNameErc1155 = ""; + imaState.chainProperties.sc.strCoinNameErc1155 = ""; + process.exit( 126 ); + } + } else { + if( n1 === 0 ) + log.fatal( "Main-net ERC1155 JSON is invalid" ); + if( n2 === 0 && imaState.chainProperties.sc.strPathJsonErc1155.length > 0 ) + log.fatal( "S-Chain ERC1155 JSON is invalid" ); + + imaState.chainProperties.mn.joErc1155 = null; + imaState.chainProperties.sc.joErc1155 = null; + imaState.chainProperties.mn.strCoinNameErc1155 = ""; + imaState.chainProperties.sc.strCoinNameErc1155 = ""; + process.exit( 126 ); + } + } else { + if( imaState.chainProperties.sc.strPathJsonErc1155.length > 0 ) { + n1 = 0; + n2 = 0; + if( isPrintGathered ) { + log.information( "Loading S-Chain ERC1155 ABI from {}", + imaState.chainProperties.sc.strPathJsonErc1155 ); + } + imaState.chainProperties.sc.joErc1155 = + imaUtils.jsonFileLoad( imaState.chainProperties.sc.strPathJsonErc1155, null ); + n2 = Object.keys( imaState.chainProperties.sc.joErc1155 ).length; + + if( n2 > 0 ) { + imaState.chainProperties.sc.strCoinNameErc1155 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.sc.joErc1155 ); + n2 = imaState.chainProperties.sc.strCoinNameErc1155.length; + if( n2 > 0 ) { + if( isPrintGathered ) { + log.information( "Loaded S-Chain ERC1155 ABI {}", + imaState.chainProperties.sc.strCoinNameErc1155 ); + } + } else { + if( n2 === 0 && imaState.chainProperties.sc.strPathJsonErc1155.length > 0 ) + log.fatal( "S-Chain ERC1155 token name is not discovered(malformed JSON)" ); + imaState.chainProperties.mn.joErc1155 = null; + imaState.chainProperties.sc.joErc1155 = null; + imaState.chainProperties.mn.strCoinNameErc1155 = ""; + imaState.chainProperties.sc.strCoinNameErc1155 = ""; + process.exit( 126 ); + } + } + } + } + if( n1 !== 0 && n2 === 0 ) { + if( imaState.strAddrErc1155Explicit.length === 0 ) { + if( isPrintGathered ) { + log.warning( "IMPORTANT NOTICE: Both S-Chain ERC1155 JSON and " + + "explicit ERC1155 address are not specified" ); + } + } else { + if( isPrintGathered ) + log.attention( "IMPORTANT NOTICE: S-Chain ERC1155 ABI will be auto-generated" ); + imaState.chainProperties.sc.strCoinNameErc1155 = + imaState.chainProperties.mn.strCoinNameErc1155 ?? ""; // assume same + imaState.chainProperties.sc.joErc1155 = + JSON.parse( JSON.stringify( imaState.chainProperties.mn.joErc1155 ) ); // clone + imaState.chainProperties.sc.joErc1155[ + imaState.chainProperties.sc.strCoinNameErc1155 + "_address"] = + imaState.strAddrErc1155Explicit; // set explicit address + } + } + + if( imaState.chainProperties.tc.strPathJsonErc1155.length > 0 ) { + if( isPrintGathered ) { + log.information( "Loading S<->S Target S-Chain ERC1155 ABI from {}", + imaState.chainProperties.tc.strPathJsonErc1155 ); + } + imaState.chainProperties.tc.joErc1155 = + imaUtils.jsonFileLoad( imaState.chainProperties.tc.strPathJsonErc1155, null ); + n2 = Object.keys( imaState.chainProperties.tc.joErc1155 ).length; + if( n2 > 0 ) { + imaState.chainProperties.tc.strCoinNameErc1155 = + imaUtils.discoverCoinNameInJSON( imaState.chainProperties.tc.joErc1155 ); + n2 = imaState.chainProperties.tc.strCoinNameErc1155.length; + if( n2 > 0 ) { + if( isPrintGathered ) { + log.information( "Loaded S<->S Target S-Chain ERC1155 ABI {}", + imaState.chainProperties.tc.strCoinNameErc1155 ); + } + } else { + if( n2 === 0 && + imaState.chainProperties.tc.strPathJsonErc1155.length > 0 && + isPrintGathered + ) { + log.fatal( " S<->S Target S-Chain ERC1155 token name " + + "is not discovered(malformed JSON)" ); + } + imaState.chainProperties.tc.joErc1155 = null; + imaState.chainProperties.tc.strCoinNameErc1155 = ""; + process.exit( 126 ); + } + } + } + if( isPrintGathered && + imaState.strAddrErc1155ExplicitTarget.length === 0 && + imaState.chainProperties.tc.strCoinNameErc1155.length === 0 && + imaState.chainProperties.sc.strCoinNameErc1155.length > 0 + ) { + log.warning( "IMPORTANT NOTICE: Both S<->S Target S-Chain ERC1155 JSON and " + + "explicit ERC1155 address are not specified" ); + } +} + +function commonInitCheckGeneralArgs(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + const isPrintSecurityValues = ( !!( imaState.isPrintSecurityValues ) ); + if( isPrintGathered ) { + printAbout( true ); + log.information( "IMA AGENT is using Ethers JS version ", + att( + owaspUtils.ethersMod.ethers.version.toString().replace( "ethers/", "" ) ) ); + } + ensureHaveValue( + "App path", + path.join( __dirname, "main.js" ), false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + ensureHaveValue( + "Verbose level", + log.verboseLevelAsTextForLog( log.verboseGet() ), + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + ensureHaveValue( + "Multi-call optimizations", + imaState.isEnabledMultiCall, false, isPrintGathered, null, ( x: any ) => { + return log.yn( x ); + } ); + ensureHaveValue( + "Main-net URL", + imaState.chainProperties.mn.strURL, false, + isPrintGathered && isPrintSecurityValues, null, ( x: any ) => { + return log.u( x ); + } ); + ensureHaveValue( + "S-chain URL", + imaState.chainProperties.sc.strURL, false, + isPrintGathered && isPrintSecurityValues, null, ( x: any ) => { + return log.u( x ); + } ); + ensureHaveValue( + "S<->S Target S-chain URL", + imaState.chainProperties.tc.strURL, false, + isPrintGathered && isPrintSecurityValues, null, ( x: any ) => { + return log.u( x ); + } ); + ensureHaveValue( + "Main-net Ethereum network name", + imaState.chainProperties.mn.strChainName, false, + isPrintGathered && isPrintSecurityValues, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "S-Chain Ethereum network name", + imaState.chainProperties.sc.strChainName, false, + isPrintGathered && isPrintSecurityValues, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "S<->S Target S-Chain Ethereum network name", + imaState.chainProperties.tc.strChainName, false, + isPrintGathered && isPrintSecurityValues, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "Main-net Ethereum chain ID", + imaState.chainProperties.mn.chainId, false, + isPrintGathered && isPrintSecurityValues, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "S-Chain Ethereum chain ID", + imaState.chainProperties.sc.chainId, false, + isPrintGathered && isPrintSecurityValues, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "S<->S Target S-Chain Ethereum chain ID", + imaState.chainProperties.tc.chainId, false, + isPrintGathered && isPrintSecurityValues, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "Skale Manager ABI JSON file path", + imaState.strPathAbiJsonSkaleManager, false, isPrintGathered, null, ( x: any ) => { + return log.fmtWarning( x ); + } ); + ensureHaveValue( + "Main-net ABI JSON file path", + imaState.chainProperties.mn.strPathAbiJson, false, isPrintGathered, null, ( x: any ) => { + return log.fmtWarning( x ); + } ); + ensureHaveValue( + "S-Chain ABI JSON file path", + imaState.chainProperties.sc.strPathAbiJson, false, isPrintGathered, null, ( x: any ) => { + return log.fmtWarning( x ); + } ); + ensureHaveValue( + "S<->S Target S-Chain ABI JSON file path", + imaState.chainProperties.tc.strPathAbiJson, false, isPrintGathered, null, ( x: any ) => { + return log.fmtWarning( x ); + } ); + + try { + ensureHaveValue( "Main-net user account address", + imaState.chainProperties.mn.joAccount.address(), false, + isPrintGathered && isPrintSecurityValues ); + } catch ( err ) {} + try { + ensureHaveValue( "S-chain user account address", + imaState.chainProperties.sc.joAccount.address(), false, + isPrintGathered && isPrintSecurityValues ); + } catch ( err ) {} + try { + ensureHaveValue( + "S<->S Target S-chain user account address", + imaState.chainProperties.tc.joAccount.address(), + false, isPrintGathered ); + } catch ( err ) {} +} + +function commonInitCheckCredentialsArgs(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + const isPrintSecurityValues = ( !!( imaState.isPrintSecurityValues ) ); + try { + ensureHaveCredentials( + "Main Net", + imaState.chainProperties.mn.joAccount, false, + isPrintGathered && isPrintSecurityValues ); + } catch ( err ) {} + try { + ensureHaveCredentials( + "S-Chain", + imaState.chainProperties.sc.joAccount, false, + isPrintGathered && isPrintSecurityValues ); + } catch ( err ) {} + try { + commonInitCheckTransferAmountArgs(); + ensureHaveCredentials( + "S<->S Target S-Chain", + imaState.chainProperties.tc.joAccount, false, + isPrintGathered && isPrintSecurityValues ); + } catch ( err ) {} + if( isPrintGathered && isPrintSecurityValues ) { + if( imaState.chainProperties.mn.joAccount.strBlsKeyName ) { + ensureHaveValue( + "BLS/Main Net key name", + imaState.chainProperties.mn.joAccount.strBlsKeyName, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + } + if( imaState.chainProperties.sc.joAccount.strBlsKeyName ) { + ensureHaveValue( + "BLS/S-Chain key name", + imaState.chainProperties.sc.joAccount.strBlsKeyName, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + } + if( imaState.chainProperties.tc.joAccount.strBlsKeyName ) { + ensureHaveValue( + "BLS/Target S-Chain key name", + imaState.chainProperties.tc.joAccount.strBlsKeyName, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + } + } +} + +function commonInitCheckTransferAmountArgs(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + ensureHaveValue( + "Amount of wei to transfer", imaState.nAmountOfWei, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); +} + +function commonInitTransferringArgs(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + ensureHaveValue( + "M->S transfer block size", imaState.nTransferBlockSizeM2S, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "S->M transfer block size", imaState.nTransferBlockSizeS2M, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + if( imaState.bHaveSkaleManagerABI ) { + ensureHaveValue( + "S->S transfer block size", imaState.nTransferBlockSizeS2S, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + } + ensureHaveValue( + "M->S transfer job steps", imaState.nTransferStepsM2S, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "S->M transfer job steps", imaState.nTransferStepsS2M, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + if( imaState.bHaveSkaleManagerABI ) { + ensureHaveValue( + "S->S transfer job steps", imaState.nTransferStepsS2S, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + } + ensureHaveValue( + "M->S transactions limit", imaState.nMaxTransactionsM2S, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "S->M transactions limit", imaState.nMaxTransactionsS2M, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + if( imaState.bHaveSkaleManagerABI ) { + ensureHaveValue( + "S->S transactions limit", imaState.nMaxTransactionsS2S, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + } + ensureHaveValue( + "M->S await blocks", imaState.nBlockAwaitDepthM2S, false, + isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "S->M await blocks", imaState.nBlockAwaitDepthS2M, false, + isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + if( imaState.bHaveSkaleManagerABI ) { + ensureHaveValue( + "S->S await blocks", imaState.nBlockAwaitDepthS2S, false, + isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + } + ensureHaveValue( + "M->S minimal block age", imaState.nBlockAgeM2S, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + ensureHaveValue( + "S->M minimal block age", imaState.nBlockAgeS2M, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + if( imaState.bHaveSkaleManagerABI ) { + ensureHaveValue( + "S->S minimal block age", imaState.nBlockAgeS2S, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtNote( x ); + } ); + } + ensureHaveValue( + "Transfer loop period(seconds)", imaState.nLoopPeriodSeconds, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtSuccess( x ); + } ); + if( imaState.nTimeFrameSeconds > 0 ) { + ensureHaveValue( + "Time framing(seconds)", imaState.nTimeFrameSeconds, + false, isPrintGathered ); + ensureHaveValue( + "Next frame gap(seconds)", imaState.nNextFrameGap, + false, isPrintGathered ); + } else { + ensureHaveValue( + "Time framing", log.fmtError( "disabled" ), + false, isPrintGathered + ); + } +} + +function commonInitCheckAccessArgs(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + ensureHaveValue( + "S-Chain node number(zero based)", + imaState.nNodeNumber, false, isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); + ensureHaveValue( + "S-Chain nodes count", + imaState.nNodesCount, false, isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); +} + +function commonInitErcTokensArgs(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + if( imaState.chainProperties.tc.strCoinNameErc20.length > 0 ) { + ensureHaveValue( + "Loaded Main-net ERC20 ABI ", + imaState.chainProperties.tc.strCoinNameErc20, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + ensureHaveValue( + "Loaded S-Chain ERC20 ABI ", + imaState.chainProperties.sc.strCoinNameErc20, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + ensureHaveValue( + "Amount of tokens to transfer", + imaState.nAmountOfToken, + false, isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); + if( isPrintGathered ) { + log.information( "ERC20 explicit S-Chain address is {}", + imaState.strAddrErc20Explicit ); + } + } + if( imaState.chainProperties.tc.strCoinNameErc20.length > 0 ) { + ensureHaveValue( + "Loaded S<->S Target S-Chain ERC20 ABI ", + imaState.chainProperties.tc.strCoinNameErc20, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + } + if( imaState.chainProperties.mn.strCoinNameErc721.length > 0 ) { + ensureHaveValue( + "Loaded Main-net ERC721 ABI ", + imaState.chainProperties.mn.strCoinNameErc721, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + ensureHaveValue( + "Loaded S-Chain ERC721 ABI ", + imaState.chainProperties.sc.strCoinNameErc721, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + ensureHaveValue( + "ERC721 token id ", + imaState.idToken, false, + isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); + if( isPrintGathered ) { + log.information( "ERC721 explicit S-Chain address is {}", + imaState.strAddrErc721Explicit ); + } + } + if( imaState.chainProperties.tc.strCoinNameErc721.length > 0 ) { + ensureHaveValue( + "Loaded S<->S Target S-Chain ERC721 ABI ", + imaState.chainProperties.tc.strCoinNameErc721, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + } + if( imaState.chainProperties.mn.strCoinNameErc1155.length > 0 ) { + ensureHaveValue( "Loaded Main-net ERC1155 ABI ", + imaState.chainProperties.mn.strCoinNameErc1155, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + ensureHaveValue( "Loaded S-Chain ERC1155 ABI ", + imaState.chainProperties.sc.strCoinNameErc1155, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + try { + ensureHaveValue( "ERC1155 token id ", + imaState.idToken, false, isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); + ensureHaveValue( "ERC1155 token amount ", + imaState.nAmountOfToken, false, isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); + } catch ( e1 ) { + try { + ensureHaveValue( + "ERC1155 batch of token ids ", + imaState.idTokens, false, + isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); + ensureHaveValue( + "ERC1155 batch of token amounts ", + imaState.arrAmountsOfTokens, false, + isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); + } catch ( e2 ) { + log.warning( "Please check your params in ERC1155 transfer" ); + log.warning( "Error 1 {}", e1 ); + log.warning( "Error 2 {}", e2 ); + process.exit( 126 ); + } + } + if( isPrintGathered ) { + log.information( "ERC1155 explicit S-Chain address is {}", + imaState.strAddrErc1155Explicit ); + } + } + if( imaState.chainProperties.tc.strCoinNameErc1155.length > 0 ) { + ensureHaveValue( + "Loaded S<->S Target S-Chain ERC1155 ABI ", + imaState.chainProperties.tc.strCoinNameErc1155, + false, isPrintGathered, null, ( x: any ) => { + return att( x ); + } ); + } +} + +function commonInitGasMultipliersAndTransactionArgs(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + if( isPrintGathered ) { + log.debug( log.fmtInformation( "Main Net Gas Price Multiplier is" ), + "....................." + + ( imaState.chainProperties.mn.transactionCustomizer.gasPriceMultiplier + ? log.fmtInformation( imaState.chainProperties.mn.transactionCustomizer + .gasPriceMultiplier.toString() ) + : log.fmtError( "disabled" ) ) ); + log.debug( log.fmtInformation( "S-Chain Gas Price Multiplier is" ), + "......................" + + ( imaState.chainProperties.sc.transactionCustomizer.gasPriceMultiplier + ? log.fmtInformation( imaState.chainProperties.sc.transactionCustomizer + .gasPriceMultiplier.toString() ) + : log.fmtError( "disabled" ) ) ); + log.debug( log.fmtInformation( "Target S-Chain Gas Price Multiplier is" ), + "..............." + + ( imaState.chainProperties.tc.transactionCustomizer.gasPriceMultiplier + ? log.fmtInformation( imaState.chainProperties.tc.transactionCustomizer + .gasPriceMultiplier.toString() ) + : log.fmtError( "disabled" ) ) ); + log.debug( log.fmtInformation( "Main Net Gas Value Multiplier is" ), + "....................." + + ( imaState.chainProperties.mn.transactionCustomizer.gasMultiplier + ? log.fmtInformation( imaState.chainProperties.mn + .transactionCustomizer.gasMultiplier.toString() ) + : log.fmtNotice( "default" ) ) ); + log.debug( log.fmtInformation( "S-Chain Gas Value Multiplier is" ), + "......................" + + ( imaState.chainProperties.sc.transactionCustomizer.gasMultiplier + ? log.fmtInformation( imaState.chainProperties.sc + .transactionCustomizer.gasMultiplier.toString() ) + : log.fmtNotice( "default" ) ) ); + log.debug( log.fmtInformation( "Target S-Chain Gas Value Multiplier is" ), + "..............." + + ( imaState.chainProperties.tc.transactionCustomizer.gasMultiplier + ? log.fmtInformation( imaState.chainProperties.tc + .transactionCustomizer.gasMultiplier.toString() ) + : log.fmtNotice( "default" ) ) ); + log.debug( log.fmtInformation( "Pending work analysis(PWA) is" ), + "........................" + + ( imaState.isPWA ? log.fmtSuccess( "enabled" ) : log.fmtError( "disabled" ) ) ); + log.debug( log.fmtInformation( "Expose PWA details to log is" ), + "........................." + + ( imaState.isPrintPWA ? log.fmtSuccess( "enabled" ) : log.fmtError( "disabled" ) ) ); + log.debug( log.fmtInformation( "Oracle based gas reimbursement is" ), + "...................." + + ( imaOracleOperations.getEnabledOracle() + ? log.fmtSuccess( "enabled" ) + : log.fmtError( "disabled" ) ) ); + log.debug( log.fmtInformation( + "S-Chain to S-Chain transferring is" ) + + "..................." + ( imaState.optsS2S.isEnabled + ? log.fmtSuccess( "enabled" ) + : log.fmtError( "disabled" ) ) ); + log.debug( log.fmtInformation( "SKALE network browser file path is" ), + "................." + + ( imaState.optsS2S.strNetworkBrowserPath + ? log.fmtInformation( imaState.optsS2S.strNetworkBrowserPath ) + : log.fmtError( "N/A" ) ) ); + log.debug( log.fmtInformation( "S<->S transfer mode is" ), + "..............................." + + imaHelperAPIs.getS2STransferModeDescriptionColorized() ); + log.debug( log.fmtInformation( "IMA JSON RPC server port is" ), + ".........................." + + ( ( imaState.nJsonRpcPort > 0 ) + ? log.fmtInformation( imaState.nJsonRpcPort ) + : log.fmtError( "disabled" ) ) ); + log.debug( log.fmtInformation( "Cross-IMA mode is" ), + "...................................." + + ( imaState.isCrossImaBlsMode + ? log.fmtSuccess( "enabled" ) + : log.fmtError( "disabled" ) ) ); + log.debug( log.fmtInformation( "Dry-run is enabled" ), + "..................................." + + log.yn( imaTx.dryRunIsEnabled() ) ); + log.debug( log.fmtInformation( "Dry-run execution result is ignored" ) + + ".................." + + log.yn( imaTx.dryRunIsIgnored() ) ); + } +} + +function commonInitLoggingArgs(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + if( imaState.strLogFilePath.length > 0 ) { + ensureHaveValue( + "Log file path", + imaState.strLogFilePath, false, + isPrintGathered, null, ( x: any ) => { + return log.fmtInformation( x ); + } ); + ensureHaveValue( + "Max size of log file path", + imaState.nLogMaxSizeBeforeRotation, false, + isPrintGathered, null, ( x: any ) => { + return ( x <= 0 ) ? log.fmtWarning( "unlimited" ) : log.fmtNote( x ); + } ); + ensureHaveValue( + "Max rotated count of log files", + imaState.nLogMaxFilesCount, + false, isPrintGathered, null, ( x: any ) => { + return ( x <= 1 ) ? log.fmtWarning( "not set" ) : log.fmtNote( x ); + } ); + } +} + +function commonInitAutomaticExitArgs(): void { + const imaState: state.TIMAState = state.get(); + const isPrintGathered = ( !!( imaState.isPrintGathered ) ); + const isPrintSecurityValues = ( !!( imaState.isPrintSecurityValues ) ); + ensureHaveValue( + "Automatic exit(seconds)", + imaState.nAutoExitAfterSeconds, false, + isPrintGathered && isPrintSecurityValues ); +} + +export function commonInit(): void { + const imaState: state.TIMAState = state.get(); + commonInitPrintSysInfo(); + commonInitCheckAbiPaths(); + commonInitCheckContractPresences(); + commonInitCheckErc20(); + commonInitCheckErc721(); + commonInitCheckErc1155(); + if( log.verboseGet() > log.verboseName2Number( "debug" ) || imaState.bShowConfigMode ) { + commonInitCheckGeneralArgs(); + commonInitCheckCredentialsArgs(); + commonInitCheckTransferAmountArgs(); + commonInitTransferringArgs(); + commonInitCheckAccessArgs(); + commonInitErcTokensArgs(); + commonInitGasMultipliersAndTransactionArgs(); + commonInitLoggingArgs(); + commonInitAutomaticExitArgs(); + } +} // commonInit + +export function imaInitEthersProviders(): void { + const imaState: state.TIMAState = state.get(); + if( imaState.chainProperties.mn.strURL && + typeof imaState.chainProperties.mn.strURL === "string" && + imaState.chainProperties.mn.strURL.length > 0 + ) { + const u = imaState.chainProperties.mn.strURL; + asyncCheckUrlAtStartup( u, "Main-net" ) + .then( function(): void {} ).catch( function(): void {} ); + imaState.chainProperties.mn.ethersProvider = owaspUtils.getEthersProviderFromURL( u ); + } else { + log.warning( "No Main-net URL specified in command line arguments" + + "(needed for particular operations only)" ); + } + + if( imaState.chainProperties.sc.strURL && + typeof imaState.chainProperties.sc.strURL === "string" && + imaState.chainProperties.sc.strURL.length > 0 + ) { + const u = imaState.chainProperties.sc.strURL; + asyncCheckUrlAtStartup( u, "S-Chain" ) + .then( function(): void {} ).catch( function(): void {} ); + imaState.chainProperties.sc.ethersProvider = owaspUtils.getEthersProviderFromURL( u ); + } else { + log.warning( "No S-Chain URL specified in command line arguments" + + "(needed for particular operations only)" ); + } + + if( imaState.chainProperties.tc.strURL && + typeof imaState.chainProperties.tc.strURL === "string" && + imaState.chainProperties.tc.strURL.length > 0 + ) { + const u = imaState.chainProperties.tc.strURL; + asyncCheckUrlAtStartup( u, "S<->S Target S-Chain" ) + .then( function(): void {} ).catch( function(): void {} ); + imaState.chainProperties.tc.ethersProvider = owaspUtils.getEthersProviderFromURL( u ); + } else { + log.warning( "No S<->S Target S-Chain URL specified in command line arguments" + + "(needed for particular operations only)" ); + } +} // imaInitEthersProviders + +function initContractsIMA(): void { + const imaState: state.TIMAState = state.get(); + if( imaState.chainProperties.mn.bHaveAbiIMA ) { + const cp = imaState.chainProperties.mn; + if( cp.ethersProvider ) { + const ep: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider = cp.ethersProvider; + const joABI = cp.joAbiIMA; + imaState.joDepositBoxETH = new owaspUtils.ethersMod.ethers.Contract( + joABI.deposit_box_eth_address, joABI.deposit_box_eth_abi, ep ); // only main net + imaState.joDepositBoxERC20 = new owaspUtils.ethersMod.ethers.Contract( + joABI.deposit_box_erc20_address, joABI.deposit_box_erc20_abi, ep ); // only main net + imaState.joDepositBoxERC721 = new owaspUtils.ethersMod.ethers.Contract( + joABI.deposit_box_erc721_address, joABI.deposit_box_erc721_abi, ep + ); // only main net + imaState.joDepositBoxERC1155 = new owaspUtils.ethersMod.ethers.Contract( + joABI.deposit_box_erc1155_address, joABI.deposit_box_erc1155_abi, ep ); + // only main net + imaState.joDepositBoxERC721WithMetadata = new owaspUtils.ethersMod.ethers.Contract( + joABI.deposit_box_erc721_with_metadata_address, + joABI.deposit_box_erc721_with_metadata_abi, ep ); // only main net + imaState.joCommunityPool = new owaspUtils.ethersMod.ethers.Contract( + joABI.community_pool_address, joABI.community_pool_abi, ep ); // only main net + imaState.joLinker = new owaspUtils.ethersMod.ethers.Contract( + joABI.linker_address, joABI.linker_abi, ep ); // only main net + imaState.joMessageProxyMainNet = new owaspUtils.ethersMod.ethers.Contract( + joABI.message_proxy_mainnet_address, joABI.message_proxy_mainnet_abi, ep ); + } + } + if( imaState.chainProperties.sc.bHaveAbiIMA ) { + const cp = imaState.chainProperties.sc; + if( cp.ethersProvider ) { + const ep: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider | null = + cp.ethersProvider; + const joABI = cp.joAbiIMA; + imaState.joTokenManagerETH = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_eth_address, joABI.token_manager_eth_abi, ep ); // only s-chain + imaState.joTokenManagerERC20 = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_erc20_address, joABI.token_manager_erc20_abi, + ep ); // only s-chain + imaState.joTokenManagerERC721 = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_erc721_address, joABI.token_manager_erc721_abi, + ep ); // only s-chain + imaState.joTokenManagerERC1155 = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_erc1155_address, joABI.token_manager_erc1155_abi, + ep ); // only s-chain + imaState.joTokenManagerERC721WithMetadata = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_erc721_with_metadata_address, + joABI.token_manager_erc721_with_metadata_abi, ep ); // only s-chain + imaState.joCommunityLocker = new owaspUtils.ethersMod.ethers.Contract( + joABI.community_locker_address, joABI.community_locker_abi, ep ); // only s-chain + imaState.joMessageProxySChain = new owaspUtils.ethersMod.ethers.Contract( + joABI.message_proxy_chain_address, joABI.message_proxy_chain_abi, ep ); + imaState.joTokenManagerLinker = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_linker_address, joABI.token_manager_linker_abi, ep ); + imaState.joEthErc20 = new owaspUtils.ethersMod.ethers.Contract( + joABI.eth_erc20_address, joABI.eth_erc20_abi, ep ); // only s-chain + } + } + if( imaState.chainProperties.tc.bHaveAbiIMA ) { + const cp = imaState.chainProperties.tc; + if( cp.ethersProvider ) { + const ep: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider = cp.ethersProvider; + const joABI = cp.joAbiIMA; + imaState.joTokenManagerETHTarget = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_eth_address, joABI.token_manager_eth_abi, ep ); // only s-chain + imaState.joTokenManagerERC20Target = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_erc20_address, joABI.token_manager_erc20_abi, + ep ); // only s-chain + imaState.joTokenManagerERC721Target = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_erc721_address, joABI.token_manager_erc721_abi, + ep ); // only s-chain + imaState.joTokenManagerERC1155Target = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_erc1155_address, joABI.token_manager_erc1155_abi, + ep ); // only s-chain + imaState.joTokenManagerERC721WithMetadataTarget = + new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_erc721_with_metadata_address, + joABI.token_manager_erc721_with_metadata_abi, ep ); // only s-chain + imaState.joCommunityLockerTarget = new owaspUtils.ethersMod.ethers.Contract( + joABI.community_locker_address, joABI.community_locker_abi, ep ); // only s-chain + imaState.joMessageProxySChainTarget = new owaspUtils.ethersMod.ethers.Contract( + joABI.message_proxy_chain_address, joABI.message_proxy_chain_abi, ep ); + imaState.joTokenManagerLinkerTarget = new owaspUtils.ethersMod.ethers.Contract( + joABI.token_manager_linker_address, joABI.token_manager_linker_abi, ep ); + imaState.joEthErc20Target = new owaspUtils.ethersMod.ethers.Contract( + joABI.eth_erc20_address, joABI.eth_erc20_abi, ep ); // only s-chain + } + } +} + +function initContractsSkaleManager(): void { + const imaState: state.TIMAState = state.get(); + if( imaState.bHaveSkaleManagerABI ) { + const cp = imaState.chainProperties.mn; + if( cp.ethersProvider ) { + const ep: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider = cp.ethersProvider; + const joABI = imaState.joAbiSkaleManager; + imaState.joConstantsHolder = new owaspUtils.ethersMod.ethers.Contract( + joABI.constants_holder_address, joABI.constants_holder_abi, ep ); + imaState.joNodes = new owaspUtils.ethersMod.ethers.Contract( + joABI.nodes_address, joABI.nodes_abi, ep ); + imaState.joKeyStorage = new owaspUtils.ethersMod.ethers.Contract( + joABI.key_storage_address, joABI.key_storage_abi, ep ); + imaState.joSChains = new owaspUtils.ethersMod.ethers.Contract( + joABI.schains_address, joABI.schains_abi, ep ); + imaState.joSChainsInternal = new owaspUtils.ethersMod.ethers.Contract( + joABI.schains_internal_address, joABI.schains_internal_abi, ep ); + imaState.joSkaleDKG = new owaspUtils.ethersMod.ethers.Contract( + joABI.skale_d_k_g_address, joABI.skale_d_k_g_abi, ep ); + imaState.joSkaleManager = new owaspUtils.ethersMod.ethers.Contract( + joABI.skale_manager_address, joABI.skale_manager_abi, ep ); + imaState.joSkaleToken = new owaspUtils.ethersMod.ethers.Contract( + joABI.skale_token_address, joABI.skale_token_abi, ep ); + imaState.joValidatorService = new owaspUtils.ethersMod.ethers.Contract( + joABI.validator_service_address, joABI.validator_service_abi, ep ); + imaState.joWallets = new owaspUtils.ethersMod.ethers.Contract( + joABI.wallets_address, joABI.wallets_abi, ep ); + } + } +} + +export function initContracts(): void { + imaInitEthersProviders(); + initContractsIMA(); + initContractsSkaleManager(); + commonInitPrintFoundContracts(); +} diff --git a/src/clpTools.ts b/src/clpTools.ts new file mode 100644 index 00000000..e970b0ce --- /dev/null +++ b/src/clpTools.ts @@ -0,0 +1,1837 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file clpTools.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as owaspUtils from "./owaspUtils.js"; +import * as log from "./log.js"; +import * as imaCLI from "./cli.js"; +import * as rpcCall from "./rpcCall.js"; +import * as state from "./state.js"; +import * as IMA from "./imaCore.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaGasUsage from "./imaGasUsageOperations.js"; +import * as imaReimbursement from "./imaReimbursementOperations.js"; +import * as imaReg from "./imaRegistrationOperations.js"; +import * as imaEth from "./imaEthOperations.js"; +import * as imaToken from "./imaTokenOperations.js"; +import * as skaleObserver from "./observer.js"; +import * as discoveryTools from "./discoveryTools.js"; +import * as loop from "./loop.js"; +import * as imaUtils from "./utils.js"; +import * as imaBLS from "./bls.js"; +import type * as imaTx from "./imaTx.js"; + +export async function registerAll( isPrintSummaryRegistrationCosts: boolean ): Promise < boolean > { + if( !await registerStep1( false ) ) + return false; + if( isPrintSummaryRegistrationCosts ) + printSummaryRegistrationCosts( log ); + return true; +} + +export async function checkRegistrationAll(): Promise { + const b1 = await checkRegistrationStep1(); + return b1; +} + +export interface TRegistrationCostInformation { + mn: any[] + sc: any[] +} + +const gInfoRegistrationCost: TRegistrationCostInformation = { + mn: [], + sc: [] +}; + +export async function registerStep1( isPrintSummaryRegistrationCosts: boolean ): Promise { + const imaState: state.TIMAState = state.get(); + imaCLI.initContracts(); + const strLogPrefix = "Reg 1: "; + log.information( "{p}Will check chain registration now...", strLogPrefix ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + let bSuccess = await imaReg.checkIsRegisteredSChainInDepositBoxes( // step 1 + imaState.chainProperties.mn.ethersProvider, + imaState.joLinker, + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.strChainName + ); + log.information( "{p}Chain is {}", strLogPrefix, + log.posNeg( bSuccess, "already registered", "not registered yet" ) ); + if( bSuccess ) + return true; + const jarrReceipts: any = + await imaReg.registerSChainInDepositBoxes( // step 1 + imaState.chainProperties.mn.ethersProvider, + imaState.joLinker, + imaState.chainProperties.mn.joAccount, + imaState.joTokenManagerETH, // only s-chain + imaState.joTokenManagerERC20, // only s-chain + imaState.joTokenManagerERC721, // only s-chain + imaState.joTokenManagerERC1155, // only s-chain + imaState.joTokenManagerERC721WithMetadata, // only s-chain + imaState.joCommunityLocker, // only s-chain + imaState.joTokenManagerLinker, // only s-chain + imaState.chainProperties.sc.strChainName, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.transactionCustomizer //, + ); + bSuccess = !!( ( jarrReceipts != null && jarrReceipts.length > 0 ) ); + log.information( "{p}Chain was {}", strLogPrefix, + log.posNeg( bSuccess, "registered successfully", "not registered" ) ); + if( bSuccess ) { + gInfoRegistrationCost.mn = + gInfoRegistrationCost.mn.concat( gInfoRegistrationCost.mn, jarrReceipts ); + } + if( isPrintSummaryRegistrationCosts ) + printSummaryRegistrationCosts(); + if( !bSuccess ) { + const nRetCode = 163; + log.fatal( "{p}failed to register S-Chain in deposit box, will return code {}", + strLogPrefix, nRetCode ); + process.exit( nRetCode ); + } + return true; +} + +export async function checkRegistrationStep1(): Promise { + const imaState: state.TIMAState = state.get(); + imaCLI.initContracts(); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + const bRetVal = await imaReg.checkIsRegisteredSChainInDepositBoxes( // step 1 + imaState.chainProperties.mn.ethersProvider, + imaState.joLinker, + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.strChainName + ); + return bRetVal; +} + +export function printSummaryRegistrationCosts( details?: any ): void { + if( !details ) + details = log; + imaGasUsage.printGasUsageReportFromArray( + "Main Net REGISTRATION", gInfoRegistrationCost.mn, details ); + imaGasUsage.printGasUsageReportFromArray( + "S-Chain REGISTRATION", gInfoRegistrationCost.sc, details ); +} + +export function commandLineTaskRegister(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Full registration(all steps)", + fn: async function(): Promise < boolean > { + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); // registerAll + return await registerAll( true ); + } + } ); +} + +export function commandLineTaskRegister1(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Registration step 1, register S-Chain in deposit box", + fn: async function(): Promise < boolean > { + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); // registerStep1 + return await registerStep1( true ); + } + } ); +} + +export function commandLineTaskCheckRegistration(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Full registration status check(all steps)", + fn: async function(): Promise < boolean > { + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); // checkRegistrationAll + const b = await checkRegistrationAll(); + // nExitCode is: 0 - OKay - registered; non-zero - not registered or error + const nExitCode = b ? 0 : 150; + log.notice( "Exiting with code {}", nExitCode ); + process.exit( nExitCode ); + } + } ); +} + +export function commandLineTaskCheckRegistration1(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Registration status check step 1, register S-Chain in deposit box", + fn: async function(): Promise < boolean > { + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); // checkRegistrationStep1 + const b = await checkRegistrationStep1(); + // nExitCode is: 0 - OKay - registered; non-zero - not registered or error + const nExitCode = b ? 0 : 152; + log.notice( "Exiting with code {}", nExitCode ); + process.exit( nExitCode ); + } + } ); +} + +export function commandLineTaskMintErc20(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "mint ERC20", + fn: async function(): Promise < boolean > { + let bMintIsOK = false; + if( imaState.chainProperties.tc.strCoinNameErc20.length > 0 ) { + try { + const strAddressMintTo = // same as caller/transaction signer + imaState.chainProperties.tc.joAccount.address(); + if( !imaState.chainProperties.tc.ethersProvider ) + throw new Error( "No provider for target chain" ); + bMintIsOK = + !!await imaToken.mintErc20( + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.tc.chainId.toString(), + imaState.chainProperties.tc.strChainName, + imaState.chainProperties.tc.joAccount, + strAddressMintTo, + imaState.nAmountOfToken, + imaState.chainProperties.tc.joErc20[imaState.chainProperties + .tc.strCoinNameErc20 + "_address"], + imaState.chainProperties.tc.joErc20[imaState.chainProperties + .tc.strCoinNameErc20 + "_abi"], + imaState.chainProperties.tc.transactionCustomizer + ); + } catch ( err ) { + bMintIsOK = false; + } + } + return bMintIsOK; + } + } ); +} + +export function commandLineTaskMintErc721(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "mint ERC721", + fn: async function(): Promise < boolean > { + let bMintIsOK = false; + if( imaState.chainProperties.tc.strCoinNameErc721.length > 0 ) { + try { + const strAddressMintTo = // same as caller/transaction signer + imaState.chainProperties.tc.joAccount.address(); + const idTokens: any[] = + ( imaState.haveArrayOfTokenIdentifiers && imaState.idTokens ) + ? imaState.idTokens + : []; + if( imaState.haveOneTokenIdentifier ) + idTokens.push( imaState.idToken ); + if( idTokens.length > 0 ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + if( !imaState.chainProperties.tc.ethersProvider ) + throw new Error( "No provider for target chain" ); + bMintIsOK = + !!await imaToken.mintErc721( + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.tc.chainId.toString(), + imaState.chainProperties.tc.strChainName, + imaState.chainProperties.tc.joAccount, + strAddressMintTo, + idToken, + imaState.chainProperties.tc.joErc721[imaState + .chainProperties.tc.strCoinNameErc721 + "_address"], + imaState.chainProperties.tc.joErc721[imaState + .chainProperties.tc.strCoinNameErc721 + "_abi"], + imaState.chainProperties.tc.transactionCustomizer + ); + } + } + } catch ( err ) { + bMintIsOK = false; + } + } + return bMintIsOK; + } + } ); +} + +export function commandLineTaskMintErc1155(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "mint ERC1155", + fn: async function(): Promise < boolean > { + let bMintIsOK = false; + if( imaState.chainProperties.tc.strCoinNameErc1155.length > 0 ) { + try { + const strAddressMintTo = // same as caller/transaction signer + imaState.chainProperties.tc.joAccount.address(); + const idTokens: any[] = + ( imaState.haveArrayOfTokenIdentifiers && imaState.idTokens ) + ? imaState.idTokens + : []; + if( imaState.haveOneTokenIdentifier ) + idTokens.push( imaState.idToken ); + if( idTokens.length > 0 ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + if( !imaState.chainProperties.tc.ethersProvider ) + throw new Error( "No provider for target chain" ); + bMintIsOK = + !!await imaToken.mintErc1155( + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.tc.chainId.toString(), + imaState.chainProperties.tc.strChainName, + imaState.chainProperties.tc.joAccount, + strAddressMintTo, + idToken, + imaState.nAmountOfToken, + imaState.chainProperties.tc + .joErc1155[imaState.chainProperties.tc + .strCoinNameErc1155 + "_address"], + imaState.chainProperties.tc + .joErc1155[imaState.chainProperties.tc + .strCoinNameErc1155 + "_abi"], + imaState.chainProperties.tc.transactionCustomizer + ); + } + } + } catch ( err ) { + bMintIsOK = false; + } + } + return bMintIsOK; + } + } ); +} + +export function commandLineTaskBurnErc20(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "burn ERC20", + fn: async function(): Promise < boolean > { + let bBurnIsOK = false; + if( imaState.chainProperties.tc.strCoinNameErc20.length > 0 ) { + try { + const strAddressBurnFrom = // same as caller/transaction signer + imaState.chainProperties.tc.joAccount.address(); + if( !imaState.chainProperties.tc.ethersProvider ) + throw new Error( "No provider for target chain" ); + bBurnIsOK = + !!await imaToken.burnErc20( + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.tc.chainId.toString(), + imaState.chainProperties.tc.strChainName, + imaState.chainProperties.tc.joAccount, + strAddressBurnFrom, + imaState.nAmountOfToken, + imaState.chainProperties.tc + .joErc20[imaState.chainProperties + .tc.strCoinNameErc20 + "_address"], + imaState.chainProperties.tc + .joErc20[imaState.chainProperties + .tc.strCoinNameErc20 + "_abi"], + imaState.chainProperties.tc.transactionCustomizer + ); + } catch ( err ) { + bBurnIsOK = false; + } + } + return bBurnIsOK; + } + } ); +} + +export function commandLineTaskBurnErc721(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "burn ERC721", + fn: async function(): Promise < boolean > { + let bBurnIsOK = false; + if( imaState.chainProperties.tc.strCoinNameErc721.length > 0 ) { + try { + const idTokens: any[] = + ( imaState.haveArrayOfTokenIdentifiers && imaState.idTokens ) + ? imaState.idTokens + : []; + if( imaState.haveOneTokenIdentifier ) + idTokens.push( imaState.idToken ); + if( idTokens.length > 0 ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + if( !imaState.chainProperties.tc.ethersProvider ) + throw new Error( "No provider for target chain" ); + bBurnIsOK = + !!await imaToken.burnErc721( + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.tc.chainId.toString(), + imaState.chainProperties.tc.strChainName, + imaState.chainProperties.tc.joAccount, + idToken, + imaState.chainProperties.tc + .joErc721[imaState.chainProperties + .tc.strCoinNameErc721 + "_address"], + imaState.chainProperties.tc + .joErc721[imaState.chainProperties + .tc.strCoinNameErc721 + "_abi"], + imaState.chainProperties.tc.transactionCustomizer + ); + } + } + } catch ( err ) { + bBurnIsOK = false; + } + } + return bBurnIsOK; + } + } ); +} + +export function commandLineTaskBurnErc1155(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "burn ERC1155", + fn: async function(): Promise < boolean > { + let bBurnIsOK = false; + if( imaState.chainProperties.tc.strCoinNameErc1155.length > 0 ) { + try { + const strAddressBurnFrom = // same as caller/transaction signer + imaState.chainProperties.tc.joAccount.address(); + const idTokens: any[] = + ( imaState.haveArrayOfTokenIdentifiers && imaState.idTokens ) + ? imaState.idTokens + : []; + if( imaState.haveOneTokenIdentifier ) + idTokens.push( imaState.idToken ); + if( idTokens.length > 0 ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + if( !imaState.chainProperties.tc.ethersProvider ) + throw new Error( "No provider for target chain" ); + bBurnIsOK = + !!await imaToken.burnErc1155( + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.tc.chainId.toString(), + imaState.chainProperties.tc.strChainName, + imaState.chainProperties.tc.joAccount, + strAddressBurnFrom, + idToken, + imaState.nAmountOfToken, + imaState.chainProperties.tc + .joErc1155[imaState.chainProperties + .tc.strCoinNameErc1155 + "_address"], + imaState.chainProperties.tc + .joErc1155[imaState.chainProperties + .tc.strCoinNameErc1155 + "_abi"], + imaState.chainProperties.tc.transactionCustomizer + ); + } + } + } catch ( err ) { + bBurnIsOK = false; + } + } + return bBurnIsOK; + } + } ); +} + +export async function commandLineTaskShowBalanceEth( + arrBalancesMN: any[], arrBalancesSC: any[], arrBalancesTC: any[] +): Promise { + const imaState: state.TIMAState = state.get(); + let assetAddress: string | null = null; + if( imaState.chainProperties.mn.ethersProvider ) { + arrBalancesMN.push( { + assetName: "RealETH", + balance: await imaEth.getBalanceEth( true, // isMainNet + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.joAccount, null ) + } ); + if( !imaState.joDepositBoxETH ) + throw new Error( "No DepositBoxETH contract" ); + arrBalancesMN.push( { + assetName: "CanReceiveETH", + balance: await imaEth.viewEthPaymentFromSchainOnMainNet( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.mn.joAccount, imaState.joDepositBoxETH ) + } ); + } + try { + assetAddress = imaState.joEthErc20 ? imaState.joEthErc20.address : ""; + } catch ( err ) { + assetAddress = null; + } + if( imaState.chainProperties.sc.ethersProvider ) { + arrBalancesSC.push( { + assetName: "S-Chain Real ETH as ERC20", + assetAddress, + balance: await imaEth.getBalanceEth( false, // isMainNet + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, imaState.joEthErc20 ) + } ); + arrBalancesSC.push( { + assetName: "S-Chain ETH Fuel", + balance: await imaEth.getBalanceEth( true, // isMainNet=true here, but we call S-Chain + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, null ) + } ); + } + if( imaState.chainProperties.tc.ethersProvider ) { + arrBalancesSC.push( { + assetName: "Target S-Chain Real ETH as ERC20", + assetAddress, + balance: await imaEth.getBalanceEth( false, // isMainNet + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.tc.joAccount, imaState.joEthErc20 ) + } ); + arrBalancesTC.push( { + assetName: "Target S-Chain ETH Fuel", + balance: await imaEth.getBalanceEth( true, // isMainNet=true here, but we call S-Chain + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.tc.chainId.toString(), + imaState.chainProperties.tc.joAccount, null ) + } ); + } +} + +export async function commandLineTaskShowBalanceErc20( + arrBalancesMN: any[], arrBalancesSC: any[], arrBalancesTC: any[] +): Promise { + const imaState: state.TIMAState = state.get(); + let assetAddress = null; + if( imaState.chainProperties.mn.ethersProvider && + imaState.chainProperties.mn.strCoinNameErc20.length > 0 + ) { + try { + assetAddress = imaState.chainProperties.mn.joErc20[ + imaState.chainProperties.mn.strCoinNameErc20 + "_address"]; + } catch ( err ) { assetAddress = null; } + arrBalancesMN.push( { + assetName: "ERC20", + assetAddress, + balance: await imaToken.getBalanceErc20( true, // isMainNet + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.mn.strCoinNameErc20, + imaState.chainProperties.mn.joErc20 ) + } ); + } + if( imaState.chainProperties.sc.ethersProvider && + imaState.chainProperties.sc.strCoinNameErc20.length > 0 + ) { + try { + assetAddress = imaState.chainProperties.sc.joErc20[ + imaState.chainProperties.sc.strCoinNameErc20 + "_address"]; + } catch ( err ) { assetAddress = null; } + arrBalancesSC.push( { + assetName: "ERC20", + assetAddress, + balance: await imaToken.getBalanceErc20( false, // isMainNet + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.sc.strCoinNameErc20, + imaState.chainProperties.sc.joErc20 ) + } ); + } + if( imaState.chainProperties.tc.ethersProvider && + imaState.chainProperties.tc.strCoinNameErc20.length > 0 + ) { + try { + assetAddress = imaState.chainProperties.tc.joErc20[ + imaState.chainProperties.tc.strCoinNameErc20 + "_address"]; + } catch ( err ) { assetAddress = null; } + arrBalancesTC.push( { + assetName: "ERC20", + assetAddress, + balance: await imaToken.getBalanceErc20( true, // isMainNet + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.tc.joAccount, + imaState.chainProperties.tc.strCoinNameErc20, + imaState.chainProperties.tc.joErc20 ) + } ); + } +} + +export async function commandLineTaskShowBalanceErc721( + arrBalancesMN: any[], arrBalancesSC: any[], arrBalancesTC: any[], idTokens: any[] +): Promise { + const imaState: state.TIMAState = state.get(); + let assetAddress = null; + if( imaState.chainProperties.mn.ethersProvider && + imaState.chainProperties.mn.strCoinNameErc721.length > 0 + ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + try { + assetAddress = imaState.chainProperties.mn.joErc721[ + imaState.chainProperties.mn.strCoinNameErc721 + "_address"]; + } catch ( err ) { assetAddress = null; } + arrBalancesMN.push( { + assetName: "ERC721", + assetAddress, + idToken, + owner: await imaToken.getOwnerOfErc721( true, // isMainNet + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.mn.strCoinNameErc721, + imaState.chainProperties.mn.joErc721, idToken ) + } ); + } + } + if( imaState.chainProperties.sc.ethersProvider && + imaState.chainProperties.sc.strCoinNameErc721.length > 0 + ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + try { + assetAddress = imaState.chainProperties.sc.joErc721[ + imaState.chainProperties.sc.strCoinNameErc721 + "_address"]; + } catch ( err ) { assetAddress = null; } + arrBalancesSC.push( { + assetName: "ERC721", + assetAddress, + idToken, + owner: await imaToken.getOwnerOfErc721( false, // isMainNet + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.sc.strCoinNameErc721, + imaState.chainProperties.sc.joErc721, idToken ) + } ); + } + } + if( imaState.chainProperties.tc.ethersProvider && + imaState.chainProperties.tc.strCoinNameErc721.length > 0 + ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + try { + assetAddress = imaState.chainProperties.tc.joErc721[ + imaState.chainProperties.tc.strCoinNameErc721 + "_address"]; + } catch ( err ) { assetAddress = null; } + arrBalancesTC.push( { + assetName: "ERC721", + assetAddress, + idToken, + owner: await imaToken.getOwnerOfErc721( false, // isMainNet + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.tc.chainId.toString(), + imaState.chainProperties.tc.joAccount, + imaState.chainProperties.tc.strCoinNameErc721, + imaState.chainProperties.tc.joErc721, idToken ) + } ); + } + } +} + +export async function commandLineTaskShowBalanceErc1155( + arrBalancesMN: any[], arrBalancesSC: any[], arrBalancesTC: any[], idTokens: any[] +): Promise { + const imaState: state.TIMAState = state.get(); + let assetAddress = null; + if( imaState.chainProperties.mn.ethersProvider && + imaState.chainProperties.mn.strCoinNameErc1155.length > 0 + ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + try { + assetAddress = imaState.chainProperties.mn.joErc1155[ + imaState.chainProperties.mn.strCoinNameErc1155 + "_address"]; + } catch ( err ) { assetAddress = null; } + arrBalancesMN.push( { + assetName: "ERC1155", + assetAddress, + idToken, + balance: await imaToken.getBalanceErc1155( true, // isMainNet + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.mn.strCoinNameErc1155, + imaState.chainProperties.mn.joErc1155, idToken ) + } ); + } + } + if( imaState.chainProperties.sc.ethersProvider && + imaState.chainProperties.sc.strCoinNameErc1155.length > 0 + ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + try { + assetAddress = imaState.chainProperties.sc.joErc1155[ + imaState.chainProperties.sc.strCoinNameErc1155 + "_address"]; + } catch ( err ) { assetAddress = null; } + arrBalancesSC.push( { + assetName: "ERC1155", + assetAddress, + idToken, + balance: await imaToken.getBalanceErc1155( false, // isMainNet + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.sc.strCoinNameErc1155, + imaState.chainProperties.sc.joErc1155, idToken ) + } ); + } + } + if( imaState.chainProperties.tc.ethersProvider && + imaState.chainProperties.tc.strCoinNameErc1155.length > 0 + ) { + for( let i = 0; i < idTokens.length; ++i ) { + const idToken = idTokens[i]; + try { + assetAddress = imaState.chainProperties.tc.joErc1155[ + imaState.chainProperties.tc.strCoinNameErc1155 + "_address"]; + } catch ( err ) { assetAddress = null; } + arrBalancesTC.push( { + assetName: "ERC1155", + assetAddress, + idToken, + balance: await imaToken.getBalanceErc1155( false, // isMainNet + imaState.chainProperties.tc.ethersProvider, + imaState.chainProperties.tc.chainId.toString(), + imaState.chainProperties.tc.joAccount, + imaState.chainProperties.tc.strCoinNameErc1155, + imaState.chainProperties.tc.joErc1155, idToken ) + } ); + } + } +} + +export function commandLineTaskShowBalance(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "show balance", + fn: async function(): Promise < boolean > { + const arrBalancesMN: any = []; + const arrBalancesSC: any = []; const arrBalancesTC: any = []; + await commandLineTaskShowBalanceEth( + arrBalancesMN, arrBalancesSC, arrBalancesTC ); + await commandLineTaskShowBalanceErc20( + arrBalancesMN, arrBalancesSC, arrBalancesTC ); + const idTokens: any[] = + ( imaState.haveArrayOfTokenIdentifiers && imaState.idTokens ) + ? imaState.idTokens + : []; + if( imaState.haveOneTokenIdentifier ) + idTokens.push( imaState.idToken ); + if( idTokens.length > 0 ) { + await commandLineTaskShowBalanceErc721( + arrBalancesMN, arrBalancesSC, arrBalancesTC, idTokens ); + await commandLineTaskShowBalanceErc1155( + arrBalancesMN, arrBalancesSC, arrBalancesTC, idTokens ); + } + if( arrBalancesMN.length > 0 || arrBalancesSC.length > 0 || arrBalancesTC.length > 0 ) { + if( arrBalancesMN.length > 0 ) { + const strAddress = imaState.chainProperties.mn.joAccount.address(); + log.information( "Main Net {} of {}:", + ( arrBalancesMN.length > 1 ? "balances" : "balance" ), strAddress ); + for( let i = 0; i < arrBalancesMN.length; ++i ) { + const bi = arrBalancesMN[i]; + log.information( " {}", + discoveryTools.formatBalanceInfo( bi, strAddress ) ); + } + } + if( arrBalancesSC.length > 0 ) { + const strAddress = imaState.chainProperties.sc.joAccount.address(); + log.information( "S-Chain {} of {}:", + ( arrBalancesMN.length > 1 ? "balances" : "balance" ), strAddress ); + for( let i = 0; i < arrBalancesSC.length; ++i ) { + const bi = arrBalancesSC[i]; + log.information( " {}", + discoveryTools.formatBalanceInfo( bi, strAddress ) ); + } + } + if( arrBalancesTC.length > 0 ) { + const strAddress = imaState.chainProperties.mn.joAccount.address(); + log.information( "Target S-Chain {} of {}:", + arrBalancesTC.length > 1 ? "balances" : "balance", strAddress ); + for( let i = 0; i < arrBalancesTC.length; ++i ) { + const bi = arrBalancesTC[i]; + log.information( " {}", + discoveryTools.formatBalanceInfo( bi, strAddress ) ); + } + } + } else + log.warning( "No balances to scan." ); + return true; + } + } ); +} + +export function commandLineTaskPaymentM2S(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "one M->S single payment", + fn: async function(): Promise < boolean > { + if( imaState.chainProperties.mn.strCoinNameErc721.length > 0 ) { + // ERC721 payment + log.information( "one M->S single ERC721 payment: {}", imaState.idToken ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + const joDepositBoxERC721 = imaState.isWithMetadata721 + ? imaState.joDepositBoxERC721WithMetadata + : imaState.joDepositBoxERC721; + if( !joDepositBoxERC721 ) + throw new Error( "No DepositBoxERC721 contract" ); + if( !imaState.joMessageProxyMainNet ) + throw new Error( "No MessageProxyMainNet contract" ); + const joTokenManagerERC721 = imaState.isWithMetadata721 + ? imaState.joTokenManagerERC721WithMetadata + : imaState.joTokenManagerERC721; + if( !joTokenManagerERC721 ) + throw new Error( "No TokenManagerERC721 contract" ); + return await imaToken.doErc721PaymentFromMainNet( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.joAccount, + joDepositBoxERC721, // only main net + imaState.joMessageProxyMainNet, // for checking logs + imaState.chainProperties.sc.strChainName, + imaState.idToken, // which ERC721 token id to send + imaState.nAmountOfWei, // how much to send + joTokenManagerERC721, // only s-chain + imaState.chainProperties.mn.strCoinNameErc721, + imaState.chainProperties.mn.joErc721, + imaState.chainProperties.sc.strCoinNameErc721, + imaState.chainProperties.sc.joErc721, + imaState.chainProperties.mn.transactionCustomizer + ); + } + if( imaState.chainProperties.tc.strCoinNameErc20.length > 0 ) { + // ERC20 payment + log.information( "one M->S single ERC20 payment: {}", imaState.nAmountOfToken ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joDepositBoxERC20 ) + throw new Error( "No DepositBoxERC20 contract" ); + if( !imaState.joMessageProxyMainNet ) + throw new Error( "No MessageProxyMainNet contract" ); + if( !imaState.joTokenManagerERC20 ) + throw new Error( "No TokenManagerERC20 contract" ); + return await imaToken.doErc20PaymentFromMainNet( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.joAccount, + imaState.joDepositBoxERC20, // only main net + imaState.joMessageProxyMainNet, // for checking logs + imaState.chainProperties.sc.strChainName, + imaState.nAmountOfToken, // how much ERC20 tokens to send + imaState.nAmountOfWei, // how much to send + imaState.joTokenManagerERC20, // only s-chain + imaState.chainProperties.tc.strCoinNameErc20, + imaState.chainProperties.mn.joErc20, + imaState.chainProperties.sc.strCoinNameErc20, + imaState.chainProperties.sc.joErc20, + imaState.chainProperties.mn.transactionCustomizer + ); + } + if( imaState.chainProperties.mn.strCoinNameErc1155.length > 0 && + imaState.idToken && imaState.idToken !== null && + imaState.idToken !== undefined && + imaState.nAmountOfToken && imaState.nAmountOfToken !== null && + imaState.nAmountOfToken !== undefined && + ( ( !imaState.idTokens ) || imaState.idTokens === null || + imaState.idTokens === undefined ) && + ( ( !imaState.arrAmountsOfTokens ) || imaState.arrAmountsOfTokens === null || + imaState.arrAmountsOfTokens === undefined ) + ) { + // ERC1155 payment + log.information( "one M->S single ERC1155 payment: {} {}", + imaState.idToken, imaState.nAmountOfToken ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joDepositBoxERC1155 ) + throw new Error( "No DepositBoxERC1155 contract" ); + if( !imaState.joMessageProxyMainNet ) + throw new Error( "No MessageProxyMainNet contract" ); + if( !imaState.joTokenManagerERC1155 ) + throw new Error( "No TokenManagerERC1155 contract" ); + return await imaToken.doErc1155PaymentFromMainNet( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.joAccount, + imaState.joDepositBoxERC1155, // only main net + imaState.joMessageProxyMainNet, // for checking logs + imaState.chainProperties.sc.strChainName, + imaState.idToken, // which ERC1155 token id to send + imaState.nAmountOfToken, // which ERC1155 token amount to send + imaState.nAmountOfWei, // how much to send + imaState.joTokenManagerERC1155, // only s-chain + imaState.chainProperties.mn.strCoinNameErc1155, + imaState.chainProperties.mn.joErc1155, + imaState.chainProperties.sc.strCoinNameErc1155, + imaState.chainProperties.sc.joErc1155, + imaState.chainProperties.mn.transactionCustomizer + ); + } + if( + imaState.chainProperties.mn.strCoinNameErc1155.length > 0 && + imaState.idTokens && + imaState.idTokens !== null && + imaState.idTokens !== undefined && + imaState.arrAmountsOfTokens && + imaState.arrAmountsOfTokens !== null && + imaState.arrAmountsOfTokens !== undefined && + ( !imaState.idToken || + imaState.idToken === null || + imaState.idToken === undefined ) && + ( !imaState.nAmountOfToken || + imaState.nAmountOfToken === null || + imaState.nAmountOfToken === undefined ) + ) { + // ERC1155 Batch payment + log.information( "one M->S single ERC1155 Batch payment: {} {}", + imaState.idTokens, imaState.arrAmountsOfTokens ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joMessageProxyMainNet ) + throw new Error( "No MessageProxyMainNet contract" ); + if( !imaState.joDepositBoxERC1155 ) + throw new Error( "No DepositBoxERC1155 contract" ); + if( !imaState.joTokenManagerERC1155 ) + throw new Error( "No TokenManagerERC1155 contract" ); + return await imaToken.doErc1155BatchPaymentFromMainNet( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.joAccount, + imaState.joDepositBoxERC1155, // only main net + imaState.joMessageProxyMainNet, // for checking logs + imaState.chainProperties.sc.strChainName, + imaState.idTokens, // which ERC1155 token id to send + imaState.arrAmountsOfTokens, // which ERC1155 token amount to send + imaState.nAmountOfWei, // how much to send + imaState.joTokenManagerERC1155, // only s-chain + imaState.chainProperties.mn.strCoinNameErc1155, + imaState.chainProperties.mn.joErc1155, + imaState.chainProperties.sc.strCoinNameErc1155, + imaState.chainProperties.sc.joErc1155, + imaState.chainProperties.mn.transactionCustomizer + ); + } + // ETH payment + log.information( "one M->S single ETH payment: {}", imaState.nAmountOfWei ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.joDepositBoxETH ) + throw new Error( "No DepositBoxETH contract" ); + if( !imaState.joMessageProxyMainNet ) + throw new Error( "No MessageProxyMainNet contract" ); + return await imaEth.doEthPaymentFromMainNet( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.joAccount, + imaState.joDepositBoxETH, // only main net + imaState.joMessageProxyMainNet, // for checking logs + imaState.chainProperties.sc.strChainName, + imaState.nAmountOfWei, // how much WEI money to send + imaState.chainProperties.mn.transactionCustomizer + ); + } + } ); +} + +export function commandLineTaskPaymentS2M(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "one S->M single payment", + fn: async function(): Promise < boolean > { + if( imaState.chainProperties.sc.strCoinNameErc721.length > 0 ) { + // ERC721 payment + log.information( "one S->M single ERC721 payment: {}", imaState.idToken ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + const joTokenManagerERC721 = imaState.isWithMetadata721 + ? imaState.joTokenManagerERC721WithMetadata + : imaState.joTokenManagerERC721; + if( !joTokenManagerERC721 ) + throw new Error( "No TokenManagerERC721 contract" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No oMessageProxySChain contract" ); + const joDepositBoxERC721 = imaState.isWithMetadata721 + ? imaState.joDepositBoxERC721WithMetadata + : imaState.joDepositBoxERC721; + if( !joTokenManagerERC721 ) + throw new Error( "No DepositBoxERC721 contract" ); + if( !joDepositBoxERC721 ) + throw new Error( "No DepositBoxERC721 contract" ); + return await imaToken.doErc721PaymentFromSChain( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.mn.joAccount, + joTokenManagerERC721, // only s-chain + imaState.joMessageProxySChain, // for checking logs + joDepositBoxERC721, // only main net + imaState.idToken, // which ERC721 token id to send + imaState.nAmountOfWei, // how much to send + imaState.chainProperties.mn.strCoinNameErc721, + imaState.chainProperties.mn.joErc721, + imaState.chainProperties.sc.strCoinNameErc721, + imaState.chainProperties.sc.joErc721, + imaState.chainProperties.sc.transactionCustomizer + ); + } + if( imaState.chainProperties.sc.strCoinNameErc20.length > 0 ) { + // ERC20 payment + log.information( "one S->M single ERC20 payment: {}", imaState.nAmountOfToken ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joTokenManagerERC20 ) + throw new Error( "No TokenManagerERC20 contract" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain contract" ); + if( !imaState.joDepositBoxERC20 ) + throw new Error( "No DepositBoxERC20 contract" ); + return await imaToken.doErc20PaymentFromSChain( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.mn.joAccount, + imaState.joTokenManagerERC20, // only s-chain + imaState.joMessageProxySChain, // for checking logs + imaState.joDepositBoxERC20, // only main net + imaState.nAmountOfToken, // how ERC20 tokens money to send + imaState.nAmountOfWei, // how much to send + imaState.chainProperties.tc.strCoinNameErc20, + imaState.chainProperties.mn.joErc20, + imaState.chainProperties.sc.strCoinNameErc20, + imaState.chainProperties.sc.joErc20, + imaState.chainProperties.sc.transactionCustomizer + ); + } + if( + imaState.chainProperties.sc.strCoinNameErc1155.length > 0 && + imaState.idToken && + imaState.idToken !== null && + imaState.idToken !== undefined && + imaState.nAmountOfToken && + imaState.nAmountOfToken !== null && + imaState.nAmountOfToken !== undefined && + ( ( !imaState.idTokens ) || + imaState.idTokens === null || + imaState.idTokens === undefined ) && + ( ( !imaState.arrAmountsOfTokens ) || + imaState.arrAmountsOfTokens === null || + imaState.arrAmountsOfTokens === undefined ) + ) { + // ERC1155 payment + log.information( "one S->M single ERC1155 payment: {} {}", + imaState.idToken, imaState.nAmountOfToken ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joTokenManagerERC1155 ) + throw new Error( "No TokenManagerERC1155 contract" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain contract" ); + if( !imaState.joDepositBoxERC1155 ) + throw new Error( "No DepositBoxERC1155 contract" ); + return await imaToken.doErc1155PaymentFromSChain( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.mn.joAccount, + imaState.joTokenManagerERC1155, // only s-chain + imaState.joMessageProxySChain, // for checking logs + imaState.joDepositBoxERC1155, // only main net + imaState.idToken, // which ERC1155 token id to send + imaState.nAmountOfToken, // which ERC1155 token amount to send + imaState.nAmountOfWei, // how much to send + imaState.chainProperties.mn.strCoinNameErc1155, + imaState.chainProperties.mn.joErc1155, + imaState.chainProperties.sc.strCoinNameErc1155, + imaState.chainProperties.sc.joErc1155, + imaState.chainProperties.sc.transactionCustomizer + ); + } + if( + imaState.chainProperties.sc.strCoinNameErc1155.length > 0 && + imaState.idTokens && + imaState.idTokens !== null && + imaState.idTokens !== undefined && + imaState.arrAmountsOfTokens && + imaState.arrAmountsOfTokens !== null && + imaState.arrAmountsOfTokens !== undefined && + ( !imaState.idToken || + imaState.idToken === null || + imaState.idToken === undefined ) && + ( !imaState.nAmountOfToken || + imaState.nAmountOfToken === null || + imaState.nAmountOfToken === undefined ) + ) { + // ERC1155 payment + log.information( "one S->M single ERC1155 payment: {} {}", + imaState.idTokens, imaState.arrAmountsOfTokens ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joTokenManagerERC1155 ) + throw new Error( "No TokenManagerERC1155 contract" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain contract" ); + if( !imaState.joDepositBoxERC1155 ) + throw new Error( "No DepositBoxERC1155 contract" ); + return await imaToken.doErc1155BatchPaymentFromSChain( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.mn.joAccount, + imaState.joTokenManagerERC1155, // only s-chain + imaState.joMessageProxySChain, // for checking logs + imaState.joDepositBoxERC1155, // only main net + imaState.idTokens, // which ERC1155 token id to send + imaState.arrAmountsOfTokens, // which ERC1155 token amount to send + imaState.nAmountOfWei, // how much to send + imaState.chainProperties.mn.strCoinNameErc1155, + imaState.chainProperties.mn.joErc1155, + imaState.chainProperties.sc.strCoinNameErc1155, + imaState.chainProperties.sc.joErc1155, + imaState.chainProperties.sc.transactionCustomizer + ); + } + // ETH payment + log.information( "one S->M single ETH payment: {}", imaState.nAmountOfWei ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joTokenManagerETH ) + throw new Error( "No TokenManagerETH contract" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain contract" ); + return await imaEth.doEthPaymentFromSChain( + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.mn.joAccount, + imaState.joTokenManagerETH, // only s-chain + imaState.joMessageProxySChain, // for checking logs + imaState.nAmountOfWei, // how much WEI money to send + imaState.chainProperties.sc.transactionCustomizer + ); + } + } ); +} + +export function commandLineTaskPaymentS2S(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "one S->S single payment", + fn: async function(): Promise < boolean > { + const isForward = imaHelperAPIs.isForwardS2S(); + const sc = imaState.chainProperties.sc; const tc = imaState.chainProperties.tc; + const ethersProviderSrc: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider | null = + isForward ? sc.ethersProvider : tc.ethersProvider; + const chainIdSrc = isForward ? sc.chainId : tc.chainId; + const joAccountSrc = isForward ? sc.joAccount : tc.joAccount; + const joTokenManagerERC20Src: owaspUtils.ethersMod.ethers.Contract | null = isForward + ? imaState.joTokenManagerERC20 + : imaState.joTokenManagerERC20Target; + const joTokenManagerERC721Src: owaspUtils.ethersMod.ethers.Contract | null = isForward + ? ( imaState.isWithMetadata721 + ? imaState.joTokenManagerERC721WithMetadata + : imaState.joTokenManagerERC721 ) + : ( imaState.isWithMetadata721 + ? imaState.joTokenManagerERC721WithMetadataTarget + : imaState.joTokenManagerERC721Target ); + + const joTokenManagerERC1155Src: owaspUtils.ethersMod.ethers.Contract | null = isForward + ? imaState.joTokenManagerERC1155 + : imaState.joTokenManagerERC1155Target; + const strChainNameDst = isForward ? tc.strChainName : sc.strChainName; + const strCoinNameErc20Src = isForward ? sc.strCoinNameErc20 : tc.strCoinNameErc20; + const strCoinNameErc721Src = isForward ? sc.strCoinNameErc721 : tc.strCoinNameErc721; + const strCoinNameErc1155Src = + isForward ? sc.strCoinNameErc1155 : tc.strCoinNameErc1155; + const joSrcErc20 = isForward ? sc.joErc20 : tc.joErc20; + const joSrcErc721 = isForward ? sc.joErc721 : tc.joErc721; + const joSrcErc1155 = isForward ? sc.joErc1155 : tc.joErc1155; + let strAddrErc20Explicit = imaState.strAddrErc20Explicit; + let strAddrErc20ExplicitTarget = imaState.strAddrErc20ExplicitTarget; + let strAddrErc721Explicit = imaState.strAddrErc721Explicit; + let strAddrErc721ExplicitTarget = imaState.strAddrErc721ExplicitTarget; + let strAddrErc1155Explicit = imaState.strAddrErc1155Explicit; + let strAddrErc1155ExplicitTarget = imaState.strAddrErc1155ExplicitTarget; + if( ( !strAddrErc20Explicit ) && sc.joErc20 && sc.strCoinNameErc20 ) + strAddrErc20Explicit = sc.joErc20[sc.strCoinNameErc20 + "_address"]; + if( ( !strAddrErc20ExplicitTarget ) && tc.joErc20 && tc.strCoinNameErc20 ) + strAddrErc20ExplicitTarget = tc.joErc20[tc.strCoinNameErc20 + "_address"]; + if( ( !strAddrErc721Explicit ) && sc.joErc721 && sc.strCoinNameErc721 ) + strAddrErc721Explicit = sc.joErc721[sc.strCoinNameErc721 + "_address"]; + if( ( !strAddrErc721ExplicitTarget ) && tc.joErc721 && tc.strCoinNameErc721 ) + strAddrErc721ExplicitTarget = tc.joErc721[tc.strCoinNameErc721 + "_address"]; + if( ( !strAddrErc1155Explicit ) && sc.joErc1155 && sc.strCoinNameErc1155 ) + strAddrErc1155Explicit = sc.joErc1155[sc.strCoinNameErc1155 + "_address"]; + if( ( !strAddrErc1155ExplicitTarget ) && tc.joErc1155 && tc.strCoinNameErc1155 ) + strAddrErc1155ExplicitTarget = tc.joErc1155[tc.strCoinNameErc1155 + "_address"]; + const strAddrErc20Dst = isForward + ? strAddrErc20ExplicitTarget + : strAddrErc20Explicit; + const strAddrErc721Dst = isForward + ? strAddrErc721ExplicitTarget + : strAddrErc721Explicit; + const strAddrErc1155Dst = isForward + ? strAddrErc1155ExplicitTarget + : strAddrErc1155Explicit; + const txCustomizer: imaTx.TransactionCustomizer = + isForward ? sc.transactionCustomizer : tc.transactionCustomizer; + if( strCoinNameErc721Src.length > 0 ) { + // ERC721 payment + log.information( "one S->S single ERC721 payment: {}", imaState.idToken ); + if( !ethersProviderSrc ) + throw new Error( "No S2S source provider" ); + if( !joTokenManagerERC721Src ) + throw new Error( "No S2S source TokenManagerERC721 contract" ); + return await imaToken.doErc721PaymentS2S( + isForward, + ethersProviderSrc, + chainIdSrc.toString(), + strChainNameDst, + joAccountSrc, + joTokenManagerERC721Src, + imaState.idToken, // which ERC721 token id to send + imaState.nAmountOfWei, // how much to send + strCoinNameErc721Src, + joSrcErc721, + strAddrErc721Dst, // only reverse payment needs it + txCustomizer + ); + } + if( strCoinNameErc20Src.length > 0 ) { + // ERC20 payment + log.information( "one S->S single ERC20 payment: {}", imaState.nAmountOfToken ); + if( !ethersProviderSrc ) + throw new Error( "No S2S source provider" ); + if( !joTokenManagerERC20Src ) + throw new Error( "No S2S source TokenManagerERC20Src contract" ); + return await imaToken.doErc20PaymentS2S( + isForward, + ethersProviderSrc, + chainIdSrc.toString(), + strChainNameDst, + joAccountSrc, + joTokenManagerERC20Src, + imaState.nAmountOfToken, // how much ERC20 tokens to send + imaState.nAmountOfWei, // how much to send + strCoinNameErc20Src, + joSrcErc20, + strAddrErc20Dst, // only reverse payment needs it + txCustomizer + ); + } + if( + strCoinNameErc1155Src.length > 0 && + imaState.idToken && + imaState.idToken !== null && + imaState.idToken !== undefined && + imaState.nAmountOfToken && + imaState.nAmountOfToken !== null && + imaState.nAmountOfToken !== undefined && + ( ( !imaState.idTokens ) || + imaState.idTokens === null || + imaState.idTokens === undefined ) && + ( ( !imaState.arrAmountsOfTokens ) || + imaState.arrAmountsOfTokens === null || + imaState.arrAmountsOfTokens === undefined ) + ) { + // ERC1155 payment + log.information( "one S->S single ERC1155 payment: {} {}", + imaState.idToken, imaState.nAmountOfToken ); + if( !ethersProviderSrc ) + throw new Error( "No S2S source provider" ); + if( !joTokenManagerERC1155Src ) + throw new Error( "No S2S source joTokenManagerERC1155 contract" ); + return await imaToken.doErc1155PaymentS2S( + isForward, + ethersProviderSrc, + chainIdSrc.toString(), + strChainNameDst, + joAccountSrc, + joTokenManagerERC1155Src, + imaState.idToken, // which ERC1155 token id to send + imaState.nAmountOfToken, // how much ERC1155 tokens to send + imaState.nAmountOfWei, // how much to send + strCoinNameErc1155Src, + joSrcErc1155, + strAddrErc1155Dst, // only reverse payment needs it + txCustomizer + ); + } + if( + strCoinNameErc1155Src.length > 0 && + imaState.idTokens && + imaState.idTokens !== null && + imaState.idTokens !== undefined && + imaState.arrAmountsOfTokens && + imaState.arrAmountsOfTokens !== null && + imaState.arrAmountsOfTokens !== undefined && + ( !imaState.idToken || + imaState.idToken === null || + imaState.idToken === undefined ) && + ( !imaState.nAmountOfToken || + imaState.nAmountOfToken === null || + imaState.nAmountOfToken === undefined ) + ) { + // ERC1155 Batch payment + log.information( "one S->S single ERC1155 Batch payment: {} {}", + imaState.idTokens, imaState.arrAmountsOfTokens ); + if( !ethersProviderSrc ) + throw new Error( "No S2S source provider" ); + if( !joTokenManagerERC1155Src ) + throw new Error( "No S2S source joTokenManagerERC1155 contract" ); + return await imaToken.doErc1155BatchPaymentS2S( + isForward, + ethersProviderSrc, + chainIdSrc.toString(), + strChainNameDst, + joAccountSrc, + joTokenManagerERC1155Src, + imaState.idTokens, // which ERC1155 token id to send + imaState.arrAmountsOfTokens, // which ERC1155 token amount to send + imaState.nAmountOfWei, // how much to send + strCoinNameErc1155Src, + joSrcErc1155, + strAddrErc1155Dst, + txCustomizer + ); + } + // ETH payment + log.information( "one S->S single ETH payment: {}", imaState.nAmountOfWei ); + log.fatal( "S->S ETH payment(s) are neither supported nor allowed" ); + process.exit( 154 ); + } + } ); +} + +export function commandLineTaskReceiveS2M(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "receive one S->M single ETH payment", + fn: async function(): Promise < boolean > { + log.information( "receive one S->M single ETH payment:" ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.joDepositBoxETH ) + throw new Error( "No DepositBoxETH contract" ); + return await imaEth.receiveEthPaymentFromSchainOnMainNet( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.joAccount, + imaState.joDepositBoxETH, + imaState.chainProperties.mn.transactionCustomizer + ); + } + } ); +} + +export function commandLineTaskViewS2M(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "view one S->M single ETH payment", + fn: async function(): Promise < boolean > { + log.information( "view one S->M single ETH payment:" ); + log.information( "receive one S->M single ETH payment:" ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.joDepositBoxETH ) + throw new Error( "No DepositBoxETH contract" ); + const xWei = await imaEth.viewEthPaymentFromSchainOnMainNet( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.mn.joAccount, + imaState.joDepositBoxETH + ); + if( xWei === null || xWei === undefined ) + return false; + const xEth = + owaspUtils.ethersMod.ethers.utils.formatEther( owaspUtils.toBN( xWei ) ); + log.success( "Main-net user can receive: {} wei = {} eth", xWei, xEth ); + return true; + } + } ); +} + +export function commandLineTaskTransferM2S(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "single M->S transfer loop", + fn: async function(): Promise < boolean > { + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); // main-net --> s-chain transfer + const joRuntimeOpts: loop.TRuntimeOpts = { + isInsideWorker: false, + idxChainKnownForS2S: 0, + cntChainsKnownForS2S: 0 + }; + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joMessageProxyMainNet ) + throw new Error( "No MessageProxyMainNet contract" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain contract" ); + return await IMA.doTransfer( // main-net --> s-chain + "M2S", + joRuntimeOpts, + imaState.chainProperties.mn.ethersProvider, + imaState.joMessageProxyMainNet, + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.ethersProvider, + imaState.joMessageProxySChain, + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.mn.strChainName, + imaState.chainProperties.sc.strChainName, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.sc.chainId.toString(), + null, + imaState.joTokenManagerETH, // for logs validation on s-chain + imaState.nTransferBlockSizeM2S, + imaState.nTransferStepsM2S, + imaState.nMaxTransactionsM2S, + imaState.nBlockAwaitDepthM2S, + imaState.nBlockAgeM2S, + imaBLS.doSignMessagesM2S, + null, + imaState.chainProperties.sc.transactionCustomizer + ); + } + } ); +} + +export function commandLineTaskTransferS2M(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "single S->M transfer loop", + fn: async function(): Promise < boolean > { + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); // s-chain --> main-net transfer + const joRuntimeOpts: loop.TRuntimeOpts = { + isInsideWorker: false, + idxChainKnownForS2S: 0, + cntChainsKnownForS2S: 0 + }; + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joMessageProxyMainNet ) + throw new Error( "No MessageProxyMainNet contract" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain contract" ); + return await IMA.doTransfer( // s-chain --> main-net + "S2M", + joRuntimeOpts, + imaState.chainProperties.sc.ethersProvider, + imaState.joMessageProxySChain, + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.mn.ethersProvider, + imaState.joMessageProxyMainNet, + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.strChainName, + imaState.chainProperties.mn.strChainName, + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.mn.chainId.toString(), + imaState.joDepositBoxETH, // for logs validation on mainnet + null, + imaState.nTransferBlockSizeS2M, + imaState.nTransferStepsS2M, + imaState.nMaxTransactionsS2M, + imaState.nBlockAwaitDepthS2M, + imaState.nBlockAgeS2M, + imaBLS.doSignMessagesS2M, + null, + imaState.chainProperties.mn.transactionCustomizer + ); + } + } ); +} + +export function commandLineTaskTransferS2S(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "single S->S transfer loop", + fn: async function(): Promise < boolean > { + if( !imaState.optsS2S.isEnabled ) + return true; + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); // s-chain --> main-net transfer + const joRuntimeOpts: loop.TRuntimeOpts = { + isInsideWorker: false, + idxChainKnownForS2S: 0, + cntChainsKnownForS2S: 0 + }; + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain contract" ); + if( !imaState.joTokenManagerETH ) + throw new Error( "No TokenManagerETH contract" ); + return await IMA.doAllS2S( // s-chain --> s-chain + joRuntimeOpts, + imaState, + skaleObserver, + imaState.chainProperties.sc.ethersProvider, + imaState.joMessageProxySChain, + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.sc.strChainName, + imaState.chainProperties.sc.chainId.toString(), + imaState.joTokenManagerETH, // for logs validation on s-chain + imaState.nTransferBlockSizeM2S, + imaState.nTransferStepsS2S, + imaState.nMaxTransactionsM2S, + imaState.nBlockAwaitDepthM2S, + imaState.nBlockAgeM2S, + imaBLS.doSignMessagesM2S, + imaState.chainProperties.sc.transactionCustomizer + ); + } + } ); +} + +export function commandLineTaskTransfer(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Single M<->S transfer loop iteration", + fn: async function(): Promise < boolean > { + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); + const joRuntimeOpts: loop.TRuntimeOpts = { + isInsideWorker: false, + idxChainKnownForS2S: 0, + cntChainsKnownForS2S: 0 + }; + const optsLoop: loop.TLoopOptions = { + joRuntimeOpts, + isDelayFirstRun: false, + enableStepOracle: true, + enableStepM2S: true, + enableStepS2M: true, + enableStepS2S: true + }; + return await loop.singleTransferLoop( optsLoop ); + } + } ); +} + +export function commandLineTaskLoop(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "M<->S and S->S transfer loop, startup in parallel mode", + fn: async function(): Promise < boolean > { + state.setPreventExitAfterLastAction( true ); + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); // M<->S transfer loop + let isPrintSummaryRegistrationCosts = false; + if( !await checkRegistrationStep1() ) { + if( !await registerStep1( false ) ) + return false; + isPrintSummaryRegistrationCosts = true; + } + if( isPrintSummaryRegistrationCosts ) + printSummaryRegistrationCosts(); + const opts: loop.TParallelLoopRunOptions = { + imaState, + details: log.globalStream() + }; + return await loop.runParallelLoops( opts ); + } + } ); +} + +export function commandLineTaskLoopSimple(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "M<->S and S->S transfer loop, startup simple mode", + fn: async function(): Promise < boolean > { + state.setPreventExitAfterLastAction( true ); + if( !imaState.bNoWaitSChainStarted ) + await discoveryTools.waitUntilSChainStarted(); // M<->S transfer loop + let isPrintSummaryRegistrationCosts = false; + if( !await checkRegistrationStep1() ) { + if( !await registerStep1( false ) ) + return false; + isPrintSummaryRegistrationCosts = true; + } + if( isPrintSummaryRegistrationCosts ) + printSummaryRegistrationCosts(); + const joRuntimeOpts: loop.TRuntimeOpts = { + isInsideWorker: false, + idxChainKnownForS2S: 0, + cntChainsKnownForS2S: 0 + }; + const optsLoop: loop.TLoopOptions = { + joRuntimeOpts, + isDelayFirstRun: false, + enableStepOracle: true, + enableStepM2S: true, + enableStepS2M: true, + enableStepS2S: true + }; + return await loop.runTransferLoop( optsLoop ); + } + } ); +} + +async function handleBrowseSkaleModesRpcInfoResult( + strLogPrefix: string, joCall: rpcCall.TRPCCall, joIn: any, joOut: any +): Promise { + const imaState: state.TIMAState = state.get(); + log.information( "{p}S-Chain network information: {}", + strLogPrefix, joOut.result ); + let nCountReceivedImaDescriptions = 0; + const jarrNodes = joOut.result.network; + for( let i = 0; i < jarrNodes.length; ++i ) { + const joNode = jarrNodes[i]; + if( !joNode ) { + log.critical( "{p}Discovery node {} is completely unknown and will be skipped", + strLogPrefix, i ); + continue; + } + const strNodeURL = imaUtils.composeSChainNodeUrl( joNode ); + const rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + let joCall: rpcCall.TRPCCall | null = null; + try { + joCall = await rpcCall.create( strNodeURL, rpcCallOpts ); + if( !joCall ) + throw new Error( `Failed to create JSON RPC call object to ${strNodeURL}` ); + const jIn: any = { method: "skale_imaInfo", params: { } }; + if( discoveryTools.isSendImaAgentIndex() ) + jIn.params.fromImaAgentIndex = imaState.nNodeNumber; + const joOut = await joCall.call( joIn ); + ++nCountReceivedImaDescriptions; + log.information( "{p}Node {} IMA information: {}", + strLogPrefix, joNode.nodeID, joOut.result ); + await joCall.disconnect(); + } catch ( err ) { + log.fatal( "JSON RPC call to S-Chain failed, error: {err}", err ); + if( joCall ) + await joCall.disconnect(); + process.exit( 159 ); + } + } + const iv = setInterval( function(): void { + if( nCountReceivedImaDescriptions == jarrNodes.length ) { + clearInterval( iv ); + process.exit( 0 ); + } + }, 100 ); + await joCall.disconnect(); +} + +export function commandLineTaskBrowseSChain(): void { + const imaState: state.TIMAState = state.get(); + imaState.bIsNeededCommonInit = false; + imaState.arrActions.push( { + name: "Browse S-Chain network", + fn: async function(): Promise < boolean > { + const strLogPrefix = "S-Chain Browse: "; + if( imaState.chainProperties.sc.strURL.length === 0 ) { + log.fatal( "Missing S-Chain URL, please specify {}", "--url-s-chain" ); + process.exit( 155 ); + } + log.information( "{p}Downloading S-Chain network information...", strLogPrefix ); + const rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + let joCall: rpcCall.TRPCCall | null = null; + try { + joCall = await rpcCall.create( imaState.chainProperties.sc.strURL, rpcCallOpts ); + if( !joCall ) { + throw new Error( "Failed to create JSON RPC call object " + + `to ${imaState.chainProperties.sc.strURL}` ); + } + const joIn: any = { method: "skale_nodesRpcInfo", params: { } }; + if( discoveryTools.isSendImaAgentIndex() ) + joIn.params.fromImaAgentIndex = imaState.nNodeNumber; + const joOut = await joCall.call( joIn ); + await handleBrowseSkaleModesRpcInfoResult( strLogPrefix, joCall, joIn, joOut ); + } catch ( err ) { + log.fatal( "JSON RPC call to S-Chain failed, error: {err}", err ); + if( joCall ) + await joCall.disconnect(); + process.exit( 159 ); + } + return true; + } + } ); +} + +export function commandLineTaskReimbursementShowBalance(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Gas Reimbursement - Show Balance", + fn: async function(): Promise < boolean > { + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.joCommunityPool ) + throw new Error( "No CommunityPool contract" ); + await imaReimbursement.reimbursementShowBalance( + imaState.chainProperties.mn.ethersProvider, + imaState.joCommunityPool, + imaState.chainProperties.mn.joAccount.address(), + imaState.chainProperties.mn.strChainName, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.transactionCustomizer, + imaState.strReimbursementChain, + true + ); + return true; + } + } ); +} + +export function commandLineTaskReimbursementEstimateAmount(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Gas Reimbursement - Estimate Amount", + fn: async function(): Promise < boolean > { + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.joCommunityPool ) + throw new Error( "No CommunityPool contract" ); + await imaReimbursement.reimbursementEstimateAmount( + imaState.chainProperties.mn.ethersProvider, + imaState.joCommunityPool, + imaState.chainProperties.mn.joAccount.address(), + imaState.chainProperties.mn.strChainName, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.transactionCustomizer, + imaState.strReimbursementChain, + true + ); + return true; + } + } ); +} + +export function commandLineTaskReimbursementRecharge(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Gas Reimbursement - Recharge User Wallet", + fn: async function(): Promise < boolean > { + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.joCommunityPool ) + throw new Error( "No CommunityPool contract" ); + await imaReimbursement.reimbursementWalletRecharge( + imaState.chainProperties.mn.ethersProvider, + imaState.joCommunityPool, + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.mn.strChainName, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.transactionCustomizer, + imaState.strReimbursementChain, + imaState.nReimbursementRecharge + ); + return true; + } + } ); +} + +export function commandLineTaskReimbursementWithdraw(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Gas Reimbursement - Withdraw User Wallet", + fn: async function(): Promise < boolean > { + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.joCommunityPool ) + throw new Error( "No CommunityPool contract" ); + await imaReimbursement.reimbursementWalletWithdraw( + imaState.chainProperties.mn.ethersProvider, + imaState.joCommunityPool, + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.mn.strChainName, + imaState.chainProperties.mn.chainId.toString(), + imaState.chainProperties.mn.transactionCustomizer, + imaState.strReimbursementChain, + imaState.nReimbursementWithdraw + ); + return true; + } + } ); +} + +export function commandLineTaskReimbursementSetRange(): void { + const imaState: state.TIMAState = state.get(); + imaState.arrActions.push( { + name: "Gas Reimbursement - Set Minimal time interval from S2M and S2S transfers", + fn: async function(): Promise < boolean > { + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joCommunityLocker ) + throw new Error( "No CommunityLocker contract" ); + await imaReimbursement.reimbursementSetRange( + imaState.chainProperties.sc.ethersProvider, + imaState.joCommunityLocker, + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.sc.strChainName, + imaState.chainProperties.sc.chainId.toString(), + imaState.chainProperties.sc.transactionCustomizer, + imaState.strChainNameOriginChain, + imaState.nReimbursementRange + ); + return true; + } + } ); +} diff --git a/src/discoveryTools.ts b/src/discoveryTools.ts new file mode 100644 index 00000000..a37cfcae --- /dev/null +++ b/src/discoveryTools.ts @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file discoveryTools.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as rpcCall from "./rpcCall.js"; +import * as state from "./state.js"; +import * as imaUtils from "./utils.js"; +import * as threadInfo from "./threadInfo.js"; +import * as owaspUtils from "./owaspUtils.js"; + +export type TFunctionAfterDiscovery = ( + err?: Error | string | null, + joSChainNetworkInfo?: TSChainNetworkInfo | null +) => void; + +export type TFunctionAfterRediscovery = ( + status: boolean +) => void; + +export interface TBLSPublicKey { + BLSPublicKey0: string + BLSPublicKey1: string + BLSPublicKey2: string + BLSPublicKey3: string +} + +export interface TBLSCommonPublicKey { + commonBLSPublicKey0: string + commonBLSPublicKey1: string + commonBLSPublicKey2: string + commonBLSPublicKey3: string +} + +export interface TNodeImaInfo extends TBLSPublicKey, TBLSCommonPublicKey { + n: number + t: number + thisNodeIndex: number +} + +export interface TPWAStateItem { + isInProgress: boolean + ts: number +} + +export interface TPWAState { + oracle: TPWAStateItem + m2s: TPWAStateItem + s2m: TPWAStateItem + s2s: { mapS2S: any } +} + +export interface TSChainNode { + httpRpcPort?: number + httpRpcPort6?: number + httpsRpcPort?: number + httpsRpcPort6?: number + imaAgentRpcPort?: number + ip?: string + ip6?: string + nodeID: number + schainIndex: number + wsRpcPort?: number + wsRpcPort6?: number + wssRpcPort?: number + wssRpcPort6?: number + imaInfo: TNodeImaInfo + pwaState?: TPWAState +} + +export interface TThisNodeProperties { + acceptors: number + bindIP: string + bindIP6: string + "enable-admin-apis"?: boolean + "enable-debug-behavior-apis"?: boolean + "enable-performance-tracker-apis"?: boolean + "enable-personal-apis"?: true + httpRpcPort?: number + httpRpcPort6?: number + httpsRpcPort?: number + httpsRpcPort6?: number + nodeID: number + schainName: string + thisNodeIndex: number + "unsafe-transactions"?: boolean + wsRpcPort?: number + wsRpcPort6?: number + wssRpcPort?: number + wssRpcPort6?: number +} + +export interface TSChainNetworkInfo { + network: TSChainNode[] + node: TThisNodeProperties + schainID: number +} + +export interface TDiscoveryOptions { + fnAfter: TFunctionAfterDiscovery | null + isSilentReDiscovery: boolean + joPrevSChainNetworkInfo: TSChainNetworkInfo | null + nCountToWait: number + imaState: state.TIMAState + strLogPrefix: string + joSChainNetworkInfo: TSChainNetworkInfo | null + jarrNodes: TSChainNode[] + cntNodes: number + cntFailed: number + nCountReceivedImaDescriptions: number + nCountAvailable: number +} + +export function formatBalanceInfo( bi: any, strAddress: string ): string { + let s = ""; + s += log.fmtInformation( "{p}", bi.assetName ); + if( "assetAddress" in bi && + typeof bi.assetAddress === "string" && bi.assetAddress.length > 0 ) + s += log.fmtDebug( "/{}", bi.assetAddress ); + if( "idToken" in bi ) + s += log.fmtDebug( " token ID {}", bi.idToken ); + s += log.posNeg( ( bi.assetName == "ERC721" ), " owner is ", " balance is " ); + s += ( bi.assetName == "ERC721" ) + ? log.fmtInformation( "{p}", bi.owner ) + : log.fmtInformation( "{p}", bi.balance ); + if( bi.assetName == "ERC721" ) { + const isSame = + ( bi.owner.trim().toLowerCase() == strAddress.trim().toLowerCase() ); + s += " " + ( isSame + ? log.fmtSuccess( "same (as account {} specified in the command line arguments)", + strAddress ) + : log.fmtError( "different (than account {} specified in the command line arguments)", + strAddress ) + ); + } + return s; +} + +function getSChainNodesCount( joSChainNetworkInfo: TSChainNetworkInfo ): number { + try { + if( !joSChainNetworkInfo ) + return 0; + const jarrNodes = joSChainNetworkInfo.network; + const cntNodes = jarrNodes.length; + return cntNodes; + } catch ( err ) { + return 0; + } +} + +export function isSChainNodeFullyDiscovered( joNode: TSChainNode ): boolean { + if( !joNode ) + return false; + if( joNode && "imaInfo" in joNode && joNode?.imaInfo && + "t" in joNode.imaInfo && joNode.imaInfo?.t && joNode.imaInfo.t > 0 && + "n" in joNode.imaInfo && joNode.imaInfo?.n && joNode.imaInfo.n > 0 && + "BLSPublicKey0" in joNode.imaInfo && joNode.imaInfo?.BLSPublicKey0 && + joNode.imaInfo.BLSPublicKey0.length > 0 && + "BLSPublicKey1" in joNode.imaInfo && joNode.imaInfo?.BLSPublicKey1 && + joNode.imaInfo.BLSPublicKey1.length > 0 && + "BLSPublicKey2" in joNode.imaInfo && joNode.imaInfo?.BLSPublicKey2 && + joNode.imaInfo.BLSPublicKey2.length > 0 && + "BLSPublicKey3" in joNode.imaInfo && joNode.imaInfo?.BLSPublicKey3 && + joNode.imaInfo.BLSPublicKey3.length > 0 && + "commonBLSPublicKey0" in joNode.imaInfo && joNode.imaInfo?.commonBLSPublicKey0 && + joNode.imaInfo.commonBLSPublicKey0.length > 0 && + "commonBLSPublicKey1" in joNode.imaInfo && joNode.imaInfo?.commonBLSPublicKey1 && + joNode.imaInfo.commonBLSPublicKey1.length > 0 && + "commonBLSPublicKey2" in joNode.imaInfo && joNode.imaInfo?.commonBLSPublicKey2 && + joNode.imaInfo.commonBLSPublicKey2.length > 0 && + "commonBLSPublicKey3" in joNode.imaInfo && joNode.imaInfo?.commonBLSPublicKey3 && + joNode.imaInfo.commonBLSPublicKey3.length > 0 + ) + return true; + return false; +} + +export function getSChainDiscoveredNodesCount( + joSChainNetworkInfo: TSChainNetworkInfo | null ): number { + try { + if( !joSChainNetworkInfo ) + return 0; + if( !( "network" in joSChainNetworkInfo && joSChainNetworkInfo.network ) ) + return 0; + const jarrNodes = joSChainNetworkInfo.network; + const cntNodes = jarrNodes.length; + if( cntNodes <= 0 ) + return 0; + let cntDiscovered = 0; + for( let i = 0; i < cntNodes; ++i ) { + try { + const joNode = joSChainNetworkInfo.network[i]; + if( isSChainNodeFullyDiscovered( joNode ) ) + ++cntDiscovered; + } catch ( err ) { + return 0; + } + } + return cntDiscovered; + } catch ( err ) { + return 0; + } +} + +export async function waitUntilSChainStarted(): Promise { + const imaState: state.TIMAState = state.get(); + log.debug( "Checking S-Chain is accessible and sane..." ); + if( ( !imaState.chainProperties.sc.strURL ) || + imaState.chainProperties.sc.strURL.length === 0 + ) { + log.warning( "Skipped, S-Chain URL was not provided." ); + return; + } + let bSuccess = false; + let idxWaitAttempt = 0; + const isSilentReDiscovery = true; // it must be silent during S-Chain sanity check + for( ; !bSuccess; ) { + try { + log.attention( "This S-Chain discovery will be done for startup pre-requisite" ); + const nCountToWait = -1; + let isError = false; + const joSChainNetworkInfo = await discoverSChainNetwork( + null, isSilentReDiscovery, null, nCountToWait + ).catch( function( err: Error | string ): void { + log.critical( "S-Chain network discovery attempt failed: {err}", err ); + isError = true; + } ); + if( ( !isError ) && joSChainNetworkInfo && typeof joSChainNetworkInfo === "object" ) { + imaState.joSChainNetworkInfo = joSChainNetworkInfo; + bSuccess = true; + } + } catch ( err ) { + bSuccess = false; + } + if( !bSuccess ) + ++idxWaitAttempt; + if( idxWaitAttempt >= imaState.nMaxWaitSChainAttempts ) { + log.warning( "Incomplete, S-Chain sanity check failed after {} attempts.", + idxWaitAttempt ); + return; + } + await threadInfo.sleep( 1000 ); + } + log.success( "Done, S-Chain is accessible and sane." ); +} + +export function isSendImaAgentIndex(): boolean { + return true; +} + +let gTimerSChainDiscovery: any = null; +let gFlagIsInSChainDiscovery: boolean = false; + +function composeStillUnknownNodesMessage( + joSChainNetworkInfo: TSChainNetworkInfo, + cntStillUnknown: number, cntNodesOnChain: number ): string { + let strMessage = log.fmtSuccess( ", {} of {} still unknown (", + cntStillUnknown, cntNodesOnChain ); + try { + const jarrNodes = joSChainNetworkInfo.network; + let cntBad = 0; + for( let i = 0; i < jarrNodes.length; ++i ) { + const joNode = jarrNodes[i]; + try { + if( isSChainNodeFullyDiscovered( joNode ) ) + continue; + if( cntBad > 0 ) + strMessage += log.fmtSuccess( ", " ); + const strNodeURL = imaUtils.composeSChainNodeUrl( joNode ); + const strNodeDescColorized = log.fmtAttention( "#{}({url})", i, strNodeURL ); + strMessage += strNodeDescColorized; + ++cntBad; + } catch ( err ) { } + } + } catch ( err ) { } + strMessage += log.fmtSuccess( ")" ); + return strMessage; +} + +async function handlePeriodicDiscoveryAttemptActions( + isSilentReDiscovery: boolean, fnAfter: TFunctionAfterDiscovery | null +): Promise { + if( gFlagIsInSChainDiscovery ) { + log.information( "Notice: long this S-Chain re-discovery is in progress now..." ); + return; + } + const imaState: state.TIMAState = state.get(); + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + fnAfter = fnAfter ?? function(): void {}; + gFlagIsInSChainDiscovery = true; + const cntNodesOnChain = getSChainNodesCount( imaState.joSChainNetworkInfo ); + try { + let nCountToWait = ( cntNodesOnChain > 2 ) + ? Math.ceil( cntNodesOnChain * 2 / 3 + 1 ) + : cntNodesOnChain; + if( nCountToWait > cntNodesOnChain ) + nCountToWait = cntNodesOnChain; + const cntDiscovered = getSChainDiscoveredNodesCount( imaState.joSChainNetworkInfo ); + if( cntDiscovered >= cntNodesOnChain ) { + if( !isSilentReDiscovery ) { + log.information( "Everything is discovered about this S-Chain. ", + "No re-discovery is needed" ); + } + if( gTimerSChainDiscovery != null ) { + clearInterval( gTimerSChainDiscovery ); + gTimerSChainDiscovery = null; + if( !isSilentReDiscovery ) + log.information( "This S-Chain re-discovery stopped" ); + } + // fnAfter() will be called here inside async call at beginning + gFlagIsInSChainDiscovery = false; + return; + } + if( cntDiscovered < cntNodesOnChain ) { + if( !isSilentReDiscovery ) { + const cntUnDiscoveredYet = cntNodesOnChain - cntDiscovered; + log.information( "Have {} of {} nodes of this S-Chain not discovered yet " + + "on re-discovery step.", cntUnDiscoveredYet, cntNodesOnChain ); + } + } + if( !isSilentReDiscovery ) { + log.information( "This S-Chain discovery will be done for re-discover task" ); + log.information( "Will re-discover {}-nodes S-Chain network, {} node(s) already " + + "discovered...", nCountToWait, cntDiscovered ); + } + let isError = false; + const joSChainNetworkInfo: TSChainNetworkInfo = await discoverSChainNetwork( + null, isSilentReDiscovery, imaState.joSChainNetworkInfo, nCountToWait + ).catch( function( err: Error | string ): void { + isError = true; + log.critical( "S-Chain network re-discovery failed: {err}", err ); + } ) as TSChainNetworkInfo; + if( !isError ) { + const cntDiscoveredNow = getSChainDiscoveredNodesCount( joSChainNetworkInfo ); + let strMessage = + log.fmtSuccess( "S-Chain network was re-discovered, {} of {} node(s)" + + "({} nodes known)", cntDiscoveredNow, nCountToWait, cntDiscoveredNow ); + const cntStillUnknown = cntNodesOnChain - cntDiscoveredNow; + if( cntStillUnknown > 0 ) { + strMessage += composeStillUnknownNodesMessage( + joSChainNetworkInfo, cntStillUnknown, cntNodesOnChain ); + } + if( !isSilentReDiscovery ) { + strMessage += log.fmtSuccess( ", complete re-discovered S-Chain " + + "network info: {}", joSChainNetworkInfo ); + } + log.information( strMessage ); + imaState.joSChainNetworkInfo = joSChainNetworkInfo; + } + fnAfter(); + continueSChainDiscoveryInBackgroundIfNeeded( isSilentReDiscovery, null ) + .then( function(): void {} ).catch( function( err ): void { + log.error( + "Failed to continue S-chain discovery, reported error is: {err}", err ); + } ); + } catch ( err ) { } + gFlagIsInSChainDiscovery = false; + // fnAfter() will be called here inside async call at beginning + continueSChainDiscoveryInBackgroundIfNeeded( isSilentReDiscovery, fnAfter ) + .then( function(): void {} ).catch( function( err ): void { + log.error( + "Failed to continue S-chain discovery, reported error is: {err}", err ); + } ); +} + +export async function continueSChainDiscoveryInBackgroundIfNeeded( + isSilentReDiscovery: boolean, fnAfter: TFunctionAfterDiscovery | null +): Promise { + if( gTimerSChainDiscovery != null ) + return; + fnAfter = fnAfter ?? function(): void {}; + const imaState: state.TIMAState = state.get(); + if( imaState.joSChainDiscovery.repeatIntervalMilliseconds <= 0 ) { + if( !isSilentReDiscovery ) + log.information( "This S-Chain re-discovery will not be preformed" ); + fnAfter(); + return; // no S-Chain re-discovery, special mode + } + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const cntNodesOnChain = getSChainNodesCount( imaState.joSChainNetworkInfo ); + let nCountToWait = ( cntNodesOnChain > 2 ) + ? Math.ceil( cntNodesOnChain * 2 / 3 + 1 ) + : cntNodesOnChain; + if( nCountToWait > cntNodesOnChain ) + nCountToWait = cntNodesOnChain; + const cntDiscovered = getSChainDiscoveredNodesCount( imaState.joSChainNetworkInfo ); + if( cntDiscovered >= cntNodesOnChain ) { + if( !isSilentReDiscovery ) { + log.attention( "Everything is discovered about this S-Chain. " + + "No re-discovery is needed" ); + } + if( gTimerSChainDiscovery != null ) { + clearInterval( gTimerSChainDiscovery ); + gTimerSChainDiscovery = null; + if( !isSilentReDiscovery ) + log.notice( "This S-Chain re-discovery stopped" ); + } + fnAfter(); + return; + } + if( cntDiscovered < cntNodesOnChain ) { + if( !isSilentReDiscovery ) { + const cntUnDiscoveredYet = cntNodesOnChain - cntDiscovered; + log.information( "Have {} of {} nodes of this S-Chain not discovered yet before " + + "continuing re-discovery.", cntUnDiscoveredYet, cntNodesOnChain ); + } + } + gTimerSChainDiscovery = setInterval( function(): void { + handlePeriodicDiscoveryAttemptActions( isSilentReDiscovery, fnAfter ) + .then( function(): void {} ).catch( function(): void {} ); + }, imaState.joSChainDiscovery.periodicDiscoveryInterval ); +} + +function handleDiscoverSkaleImaInfoResult( + optsDiscover: TDiscoveryOptions, strNodeDescColorized: string, + joNode: any, joCall: rpcCall.TRPCCall, joIn: any, joOut: any +): void { + joNode.imaInfo = joOut.result; + if( isSChainNodeFullyDiscovered( joNode ) ) + ++optsDiscover.nCountReceivedImaDescriptions; + if( !optsDiscover.isSilentReDiscovery ) { + log.success( "{p}OK, got {} node {} IMA information({} of {}).", + optsDiscover.strLogPrefix, strNodeDescColorized, joNode.nodeID, + optsDiscover.nCountReceivedImaDescriptions, optsDiscover.cntNodes ); + } +} + +async function discoverSChainWalkNodes( optsDiscover: TDiscoveryOptions ): Promise { + optsDiscover.cntFailed = 0; + for( let i = 0; i < optsDiscover.cntNodes; ++i ) { + const nCurrentNodeIdx = owaspUtils.toInteger( i ); + const joNode = optsDiscover.jarrNodes[nCurrentNodeIdx]; + const strNodeURL = imaUtils.composeSChainNodeUrl( joNode ); + const strNodeDescColorized = log.fmtAttention( "#{}({url})", nCurrentNodeIdx, strNodeURL ); + if( !optsDiscover.isSilentReDiscovery ) { + log.information( "{p}Will try to discover S-Chain node {}...", + optsDiscover.strLogPrefix, strNodeDescColorized ); + } + try { + if( optsDiscover.joPrevSChainNetworkInfo && + "network" in optsDiscover.joPrevSChainNetworkInfo && + optsDiscover.joPrevSChainNetworkInfo.network ) { + const joPrevNode = + optsDiscover.joPrevSChainNetworkInfo.network[nCurrentNodeIdx]; + if( isSChainNodeFullyDiscovered( joPrevNode ) ) { + joNode.imaInfo = JSON.parse( JSON.stringify( joPrevNode.imaInfo ) ); + if( !optsDiscover.isSilentReDiscovery ) { + log.information( + "{p}OK, in case of {} node {} will use previous discovery result.", + optsDiscover.strLogPrefix, strNodeDescColorized, joNode.nodeID ); + } + continue; // skip this node discovery, enrich rest of nodes + } + } + } catch ( err ) { } + const rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + let joCall: rpcCall.TRPCCall | null = null; + try { + joCall = await rpcCall.create( strNodeURL, rpcCallOpts ); + if( !joCall ) + throw new Error( `Failed to create JSON RPC call object to ${strNodeURL}` ); + const joIn: any = { method: "skale_imaInfo", params: { } }; + if( isSendImaAgentIndex() ) + joIn.params.fromImaAgentIndex = optsDiscover.imaState.nNodeNumber; + const joOut = await joCall.call( joIn ); + handleDiscoverSkaleImaInfoResult( + optsDiscover, strNodeDescColorized, joNode, joCall, joIn, joOut ); + } catch ( err ) { + if( !optsDiscover.isSilentReDiscovery ) { + log.critical( + "{p}JSON RPC call(err) to S-Chain node {} failed: {err}, stack is:\n{stack}", + optsDiscover.strLogPrefix, strNodeDescColorized, err, err ); + } + ++optsDiscover.cntFailed; + if( joCall ) + await joCall.disconnect(); + } + } +} + +async function discoverSChainWait( optsDiscover: TDiscoveryOptions ): Promise { + if( !optsDiscover.isSilentReDiscovery ) { + log.debug( "{p}Waiting for response from at least {} node(s)...", + optsDiscover.strLogPrefix, optsDiscover.nCountToWait ); + } + let nWaitAttempt = 0; + const nWaitStepMilliseconds = 1 * 1000; // step can be small here + let cntWaitAttempts = Math.floor( + optsDiscover.imaState.joSChainDiscovery.repeatIntervalMilliseconds / + nWaitStepMilliseconds ) - 3; + if( cntWaitAttempts < 1 ) + cntWaitAttempts = 1; + const iv = setInterval( function(): void { + optsDiscover.nCountAvailable = + optsDiscover.cntNodes - optsDiscover.cntFailed; + // notice, below provided up-to-date count of available and fully discovered nodes: + optsDiscover.nCountReceivedImaDescriptions = + getSChainDiscoveredNodesCount( optsDiscover.joSChainNetworkInfo ); + if( !optsDiscover.isSilentReDiscovery ) { + log.debug( + "Waiting (S-Chain discovery) attempt {} of {} for S-Chain nodes, " + + "total {}, available {}, expected at least {}, discovered {}", + nWaitAttempt, cntWaitAttempts, optsDiscover.cntNodes, optsDiscover.nCountAvailable, + optsDiscover.nCountToWait, optsDiscover.nCountReceivedImaDescriptions ); + } + if( !optsDiscover.isSilentReDiscovery ) { + log.information( "{p}Have S-Chain description response about {} of {} node(s).", + optsDiscover.strLogPrefix, optsDiscover.nCountReceivedImaDescriptions, + optsDiscover.cntNodes ); + } + if( optsDiscover.nCountReceivedImaDescriptions >= optsDiscover.nCountToWait ) { + if( !optsDiscover.isSilentReDiscovery ) { + log.success( + "{p}This S-Chain discovery will finish with {} of {} node(s) discovered.", + optsDiscover.strLogPrefix, optsDiscover.nCountReceivedImaDescriptions, + optsDiscover.cntNodes ); + } + clearInterval( iv ); + if( optsDiscover.fnAfter ) + optsDiscover.fnAfter( null, optsDiscover.joSChainNetworkInfo ); + return; + } + ++nWaitAttempt; + if( nWaitAttempt >= cntWaitAttempts ) { + clearInterval( iv ); + const strErrorDescription = "S-Chain network discovery wait timeout, " + + "network will be re-discovered later"; + if( !optsDiscover.isSilentReDiscovery ) { + log.warning( "{p}This S-Chain discovery will finish due to: {err}", + optsDiscover.strLogPrefix, strErrorDescription ); + } + if( getSChainDiscoveredNodesCount( optsDiscover.joSChainNetworkInfo ) > 0 ) { + if( optsDiscover.fnAfter ) + optsDiscover.fnAfter( null, optsDiscover.joSChainNetworkInfo ); + } else { + if( optsDiscover.fnAfter ) + optsDiscover.fnAfter( new Error( strErrorDescription ), null ); + } + return; + } + if( !optsDiscover.isSilentReDiscovery ) { + log.debug( "{p}S-Chain discovery waiting attempt {} of {} for {} node answer(s)", + optsDiscover.strLogPrefix, nWaitAttempt, cntWaitAttempts, + ( optsDiscover.nCountToWait - optsDiscover.nCountReceivedImaDescriptions ) ); + } + }, nWaitStepMilliseconds ); +} + +async function handleDiscoverSkaleNodesRpcInfoResult( + optsDiscover: TDiscoveryOptions, scURL: string, + joCall: rpcCall.TRPCCall, joIn: any, joOut: any ): Promise { + if( !optsDiscover.isSilentReDiscovery ) { + log.trace( "{p}OK, got (own) S-Chain network information: {}", + optsDiscover.strLogPrefix, joOut.result ); + log.success( "{p}OK, got S-Chain {url} network information.", + optsDiscover.strLogPrefix, scURL ); + } + optsDiscover.nCountReceivedImaDescriptions = 0; + optsDiscover.joSChainNetworkInfo = joOut.result; + if( !optsDiscover.joSChainNetworkInfo ) { + const err2 = new Error( + "Got wrong response, network information description was not detected" ); + if( !optsDiscover.isSilentReDiscovery ) { + log.critical( "{p}Network was not detected via call to {url}: {err}", + optsDiscover.strLogPrefix, scURL, err2 ); + } + if( optsDiscover.fnAfter ) + optsDiscover.fnAfter( err2, null ); + await joCall.disconnect(); + throw err2; + } + optsDiscover.jarrNodes = optsDiscover.joSChainNetworkInfo.network; + optsDiscover.cntNodes = optsDiscover.jarrNodes.length; + if( optsDiscover.nCountToWait <= 0 || optsDiscover.nCountToWait >= optsDiscover.cntNodes ) { + optsDiscover.nCountToWait = ( optsDiscover.cntNodes > 2 ) + ? Math.ceil( optsDiscover.cntNodes * 2 / 3 ) + : optsDiscover.cntNodes; + } + if( optsDiscover.nCountToWait > optsDiscover.cntNodes ) + optsDiscover.nCountToWait = optsDiscover.cntNodes; + if( !optsDiscover.isSilentReDiscovery ) { + log.information( "{p}Will gather details of {} of {} node(s)...", + optsDiscover.strLogPrefix, optsDiscover.nCountToWait, optsDiscover.cntNodes ); + } + await discoverSChainWalkNodes( optsDiscover ); + optsDiscover.nCountAvailable = optsDiscover.cntNodes - optsDiscover.cntFailed; + if( !optsDiscover.isSilentReDiscovery ) { + log.debug( "Waiting for S-Chain nodes, total {}, available {}, expected at least {}", + optsDiscover.cntNodes, optsDiscover.nCountAvailable, optsDiscover.nCountToWait ); + } + if( optsDiscover.nCountAvailable < optsDiscover.nCountToWait ) { + if( !optsDiscover.isSilentReDiscovery ) { + log.critical( + "{p}Not enough nodes available on S-Chain, total {}, " + + "available {}, expected at least {}", + optsDiscover.strLogPrefix, optsDiscover.cntNodes, + optsDiscover.nCountAvailable, optsDiscover.nCountToWait ); + } + const err = new Error( "Not enough nodes available on S-Chain, " + + `total ${optsDiscover.cntNodes}, available ${optsDiscover.nCountAvailable}, ` + + `expected at least ${optsDiscover.nCountToWait}` ); + if( optsDiscover.fnAfter ) + optsDiscover.fnAfter( err, null ); + throw err; + } + let rv = false; + await discoverSChainWait( optsDiscover ).then( function(): void { + if( optsDiscover.fnAfter ) + optsDiscover.fnAfter( null, optsDiscover.joSChainNetworkInfo ); + rv = true; + } ).catch( function( err: Error | string ): void { + log.error( + "Failed to wait until S-chain discovery complete, reported error is: {err}", err ); + if( optsDiscover.fnAfter ) + optsDiscover.fnAfter( err, null ); + } ); + return rv; +} + +export async function discoverSChainNetwork( + fnAfter: TFunctionAfterDiscovery | null, isSilentReDiscovery: boolean, + joPrevSChainNetworkInfo: any, nCountToWait: number +): Promise { + const optsDiscover: TDiscoveryOptions = { + fnAfter, + isSilentReDiscovery: ( !!isSilentReDiscovery ), + joPrevSChainNetworkInfo: joPrevSChainNetworkInfo || null, + nCountToWait, + imaState: state.get(), + strLogPrefix: "S-Chain network discovery: ", + joSChainNetworkInfo: null, + jarrNodes: [], + cntNodes: 0, + cntFailed: 0, + nCountReceivedImaDescriptions: 0, + nCountAvailable: 0 + }; + if( optsDiscover.nCountToWait == null || + optsDiscover.nCountToWait == undefined || + optsDiscover.nCountToWait < 0 ) + optsDiscover.nCountToWait = 0; + if( !optsDiscover.isSilentReDiscovery ) + log.information( "{p}This S-Chain discovery will start...", optsDiscover.strLogPrefix ); + let joCall: rpcCall.TRPCCall | null = null; + try { + const scURL = optsDiscover.imaState.chainProperties.sc.strURL; + const rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + joCall = await rpcCall.create( scURL, rpcCallOpts ); + if( !joCall ) + throw new Error( `Failed to create JSON RPC call object to ${scURL}` ); + const joIn: any = { method: "skale_nodesRpcInfo", params: { } }; + if( isSendImaAgentIndex() ) + joIn.params.fromImaAgentIndex = optsDiscover.imaState.nNodeNumber; + const joOut = await joCall.call( joIn ); + await handleDiscoverSkaleNodesRpcInfoResult( + optsDiscover, scURL, joCall, joIn, joOut + ).catch( function( err: Error | string ): void { + log.critical( + "{p}JSON RPC call(in discoverSChainNetwork) error: {err}, stack is:\n{stack}", + optsDiscover.strLogPrefix, err, err ); + } ); + } catch ( err ) { + if( !optsDiscover.isSilentReDiscovery ) { + log.critical( + "{p}JSON RPC call(discoverSChainNetwork) to S-Chain failed: " + + "{err}, stack is:\n{stack}", optsDiscover.strLogPrefix, + err, err ); + } + optsDiscover.joSChainNetworkInfo = null; + if( optsDiscover.fnAfter ) + optsDiscover.fnAfter( err as Error, null ); + if( joCall ) + await joCall.disconnect(); + throw err; + } + return optsDiscover.joSChainNetworkInfo; +} + +let gIntervalPeriodicDiscovery: any = null; + +function checkPeriodicDiscoveryNoLongerNeeded( + joSChainNetworkInfo: TSChainNetworkInfo | null, isSilentReDiscovery: boolean +): boolean { + if( !joSChainNetworkInfo ) + return false; + const imaState: state.TIMAState = state.get(); + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const cntNodesOnChain = getSChainNodesCount( imaState.joSChainNetworkInfo ); + const cntAlreadyDiscovered = getSChainDiscoveredNodesCount( joSChainNetworkInfo ); + if( !isSilentReDiscovery ) { + log.notice( "Periodic S-Chain re-discovery already have {} of {} node(s) discovered", + cntAlreadyDiscovered, cntNodesOnChain ); + } + if( cntAlreadyDiscovered >= cntNodesOnChain ) { + if( gIntervalPeriodicDiscovery ) { + clearInterval( gIntervalPeriodicDiscovery ); + gIntervalPeriodicDiscovery = null; + } + return true; + } + return false; +} + +export async function doPeriodicSChainNetworkDiscoveryIfNeeded( + isSilentReDiscovery: boolean, fnAfterRediscover?: TFunctionAfterRediscovery +): Promise { + if( gIntervalPeriodicDiscovery ) + return; // already started + const imaState: state.TIMAState = state.get(); + let joPrevSChainNetworkInfo = imaState.joSChainNetworkInfo; + if( checkPeriodicDiscoveryNoLongerNeeded( + joPrevSChainNetworkInfo, isSilentReDiscovery ) ) { + if( !isSilentReDiscovery ) + log.success( "Periodic S-Chain re-discovery is not needed right from startup" ); + return; // not needed right from very beginning + } + if( !imaState.joSChainNetworkInfo ) + throw new Error( "No own S-Chain network information" ); + const cntNodesOnChain = getSChainNodesCount( imaState.joSChainNetworkInfo ); + let periodicDiscoveryInterval = imaState.joSChainDiscovery.periodicDiscoveryInterval; + if( periodicDiscoveryInterval <= 0 ) + periodicDiscoveryInterval = 5 * 60 * 1000; + if( !isSilentReDiscovery ) { + log.debug( "Periodic S-Chain re-discovery will be done with {} interval...", + periodicDiscoveryInterval ); + } + fnAfterRediscover = fnAfterRediscover ?? function( status: boolean ) { }; + gIntervalPeriodicDiscovery = setInterval( function(): void { + let nCountToWait = ( cntNodesOnChain > 2 ) + ? Math.ceil( cntNodesOnChain * 2 / 3 ) + : cntNodesOnChain; + if( nCountToWait > cntNodesOnChain ) + nCountToWait = cntNodesOnChain; + if( !isSilentReDiscovery ) + log.information( "This S-Chain discovery will be done for periodic discovery update" ); + discoverSChainNetwork( + null, isSilentReDiscovery, joPrevSChainNetworkInfo, nCountToWait ) + .then( function(): void { + joPrevSChainNetworkInfo = imaState.joSChainNetworkInfo; + if( checkPeriodicDiscoveryNoLongerNeeded( + joPrevSChainNetworkInfo, isSilentReDiscovery ) ) { + if( !isSilentReDiscovery ) + log.information( "Final periodic S-Chain re-discovery done" ); + if( fnAfterRediscover ) + fnAfterRediscover( true ); + return; // not needed anymore, all nodes completely discovered + } + if( !isSilentReDiscovery ) + log.information( "Partial periodic S-Chain re-discovery done" ); + if( fnAfterRediscover ) + fnAfterRediscover( false ); + } ).catch( function(): void {} ); + }, periodicDiscoveryInterval ); + if( !isSilentReDiscovery ) { + log.information( "Periodic S-Chain re-discovery was started with interval {}" + + " millisecond(s)", periodicDiscoveryInterval ); + } +} diff --git a/src/eventDispatcher.ts b/src/eventDispatcher.ts new file mode 100644 index 00000000..9c95e8ae --- /dev/null +++ b/src/eventDispatcher.ts @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE COOL SOCKET + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file eventDispatcher.ts + * @copyright SKALE Labs 2019-Present + */ + +export class UniversalDispatcherEvent { + type: any; + constructor ( type: any, jo: any ) { + this.type = type; + for( const [ key, value ] of Object.entries( jo ) ) { + if( key in this ) { + console.warn( "UniversalDispatcherEvent will skip", key, "data field" ); + continue; + } + const anyThis: any = this; + anyThis[key] = value; + } + } +}; + +export class EventDispatcher { + // see https://stackoverflow.com/questions/36675693/eventtarget-interface-in-safari + _listeners: any[]; + isDisposing: boolean; + isDisposed: boolean; + constructor () { + this._listeners = []; + this.isDisposed = false; + this.isDisposing = false; + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.isDisposed = true; + this.dispatchEvent( + new UniversalDispatcherEvent( "dispose", { detail: { ref: this } } ) + ); + this.removeAllEventListeners(); + } + + hasEventListener( type: any, listener: any ): boolean { + return this._listeners.some( item => item.type === type && item.listener === listener ); + } + + addEventListener( type: any, listener: any ): EventDispatcher { + if( !this.hasEventListener( type, listener ) ) { + this._listeners.push( { + type, + listener, + options: { once: false } + } ); + } + return this; + } + + removeEventListener( type: any, listener: any ): EventDispatcher { + while( true ) { + const index = ( listener != undefined ) + ? this._listeners.findIndex( + item => item.type === type && item.listener === listener ) + : this._listeners.findIndex( + item => item.type === type ); + if( index >= 0 ) { + this._listeners.splice( index, 1 ); + continue; + } + break; + } + return this; + } + + removeAllEventListeners(): EventDispatcher { + this._listeners = []; + return this; + } + + on( type: any, listener: any ): EventDispatcher { + return this.addEventListener( type, listener ); + } + + off( type: any, listener: any ): EventDispatcher { + return this.removeEventListener( type, listener ); + } + + offAll(): EventDispatcher { + return this.removeAllEventListeners(); + } + + dispatchEvent( evt: any ): EventDispatcher { + const a = this._listeners.filter( item => item.type === evt.type ); + for( const item of a ) { + const { + type, + listener, + options: { once } + } = item; + listener.call( this, evt ); + if( once === true ) + this.removeEventListener( type, listener ); + } + return this; + } +}; diff --git a/src/get_abi_json_for_main_net.sh b/src/get_abi_json_for_main_net.sh new file mode 100755 index 00000000..cb77b72e --- /dev/null +++ b/src/get_abi_json_for_main_net.sh @@ -0,0 +1,31 @@ +#!/bin/bash +#FILE_SRC="../IMA/proxy/proxy.json" +FILE_SRC="../IMA/proxy/data/pseudo_main_net_proxy.json" +FILE_DST="./abi_main_net.json" +FILE_BAK="${FILE_DST}.bak" +echo "Source file......... '${FILE_SRC}'" +echo "Destination file.... '${FILE_DST}'" +#echo "Backup file......... '${FILE_BAK}'" + +if [ ! -f "$FILE_SRC" ] +then + echo "Source file '${FILE_SRC}' not found." + exit 101 +fi + +if [ -f "$FILE_DST" ] +then + if [ -f "$FILE_BAK" ] + then + echo "Removing backup file '${FILE_BAK}'" + rm -f "$FILE_BAK" > /dev/null + fi + echo "Backing-up file '${FILE_DST}' to ${FILE_BAK}'" + mv "${FILE_DST}" "${FILE_BAK}" +fi + +echo "Copying file '${FILE_SRC}' to ${FILE_DST}'" +rm -f "$FILE_DST" > /dev/null +cp "${FILE_SRC}" "${FILE_DST}" + +echo "Done." diff --git a/src/get_abi_json_for_s_chain.sh b/src/get_abi_json_for_s_chain.sh new file mode 100755 index 00000000..7f052614 --- /dev/null +++ b/src/get_abi_json_for_s_chain.sh @@ -0,0 +1,31 @@ +#!/bin/bash +#FILE_SRC="../IMA/proxy/proxy.json" +FILE_SRC="../IMA/proxy/data/local_proxy.json" +FILE_DST="./abi_s_chain.json" +FILE_BAK="${FILE_DST}.bak" +echo "Source file......... '${FILE_SRC}'" +echo "Destination file.... '${FILE_DST}'" +#echo "Backup file......... '${FILE_BAK}'" + +if [ ! -f "$FILE_SRC" ] +then + echo "Source file '${FILE_SRC}' not found." + exit 101 +fi + +if [ -f "$FILE_DST" ] +then + if [ -f "$FILE_BAK" ] + then + echo "Removing backup file '${FILE_BAK}'" + rm -f "$FILE_BAK" > /dev/null + fi + echo "Backing-up file '${FILE_DST}' to ${FILE_BAK}'" + mv "${FILE_DST}" "${FILE_BAK}" +fi + +echo "Copying file '${FILE_SRC}' to ${FILE_DST}'" +rm -f "$FILE_DST" > /dev/null +cp "${FILE_SRC}" "${FILE_DST}" + +echo "Done." diff --git a/src/imaCore.ts b/src/imaCore.ts new file mode 100644 index 00000000..bc698ddd --- /dev/null +++ b/src/imaCore.ts @@ -0,0 +1,1310 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file index.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as loop from "./loop.js"; +import * as pwa from "./pwa.js"; +import * as state from "./state.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaTx from "./imaTx.js"; +import * as imaGasUsage from "./imaGasUsageOperations.js"; +import * as imaEventLogScan from "./imaEventLogScan.js"; +import * as imaTransferErrorHandling from "./imaTransferErrorHandling.js"; +import * as skaleObserver from "./observer.js"; +import * as threadInfo from "./threadInfo.js"; + +export type TFunctionAfterSigningMessages = + ( err: Error | string | null, jarrMessages: any[], joGlueResult?: any | null + ) => Promise < void >; +export type TFunctionDoSignMessages = + ( nTransferLoopCounter: number, jarrMessages: any[], nIdxCurrentMsgBlockStart: number, + chainNameSrc: string, joExtraSignOpts?: loop.TExtraSignOpts | null, + fnAfter?: TFunctionAfterSigningMessages ) => Promise < void >; + +export interface TTransferOptions { + strDirection: string + joRuntimeOpts: loop.TRuntimeOpts + ethersProviderSrc: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider + joMessageProxySrc: owaspUtils.ethersMod.Contract + joAccountSrc: state.TAccount + ethersProviderDst: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider + joMessageProxyDst: owaspUtils.ethersMod.Contract + joAccountDst: state.TAccount + chainNameSrc: string + chainNameDst: string + chainIdSrc: string + chainIdDst: string + joDepositBoxMainNet: owaspUtils.ethersMod.Contract | null // for logs validation on mainnet + joTokenManagerSChain: owaspUtils.ethersMod.Contract | null // for logs validation on s-chain + nTransactionsCountInBlock: number + nTransferSteps: number + nMaxTransactionsCount: number + nBlockAwaitDepth: number + nBlockAge: number + fnSignMessages: TFunctionDoSignMessages + joExtraSignOpts?: loop.TExtraSignOpts | null + transactionCustomizerDst: imaTx.TransactionCustomizer + imaState: state.TIMAState + nTransferLoopCounter: number + strTransferErrorCategoryName: string + strGatheredDetailsName: string + details: log.TLogger + jarrReceipts: any[] + bErrorInSigningMessages: boolean + strLogPrefixShort: string + strLogPrefix: string + nStepsDone: number + strActionName: string + nIdxCurrentMsg: number + nOutMsgCnt: number + nIncMsgCnt: number + cntProcessed: number + arrMessageCounters: any[] + jarrMessages: any[] + nIdxCurrentMsgBlockStart: number + cntAccumulatedForBlock: number + arrLogRecordReferences: any[] + cntNodesShouldPass: number + cntNodesMayFail: number +} + +export interface TOutgoingMessageAnalysisOptions { + idxMessage: number + idxImaMessage: number + joMessage: any + joNode: skaleObserver.TSChainNode | null + idxNode: number + cntNodes: number + cntPassedNodes: number + cntFailedNodes: number +} + +log.enableColorization( false ); +log.addStdout(); + +const perMessageGasForTransfer = 1000000; +const additionalS2MTransferOverhead = 200000; + +async function findOutReferenceLogRecord( + details: log.TLogger, strLogPrefix: string, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joMessageProxy: owaspUtils.ethersMod.ethers.Contract, + bnBlockId: any, nMessageNumberToFind: number, isVerbose?: boolean +): Promise { + const bnMessageNumberToFind = owaspUtils.toBN( nMessageNumberToFind.toString() ); + const strEventName = "PreviousMessageReference"; + const arrLogRecords = await imaEventLogScan.safeGetPastEventsProgressive( + details, strLogPrefix, ethersProvider, 10, joMessageProxy, strEventName, + bnBlockId, bnBlockId, joMessageProxy.filters[strEventName]() ); + const cntLogRecord = arrLogRecords.length; + if( isVerbose ) { + details.debug( "{p}Got {} log record(s) ({}) with data: {}", + strLogPrefix, cntLogRecord, strEventName, arrLogRecords ); + } + for( let idxLogRecord = 0; idxLogRecord < cntLogRecord; ++idxLogRecord ) { + const joEvent = arrLogRecords[idxLogRecord]; + const ev = { + currentMessage: joEvent.args[0], + previousOutgoingMessageBlockId: joEvent.args[1] + }; + const joReferenceLogRecord: any = { + currentMessage: ev.currentMessage, + previousOutgoingMessageBlockId: ev.previousOutgoingMessageBlockId, + currentBlockId: bnBlockId + }; + const bnCurrentMessage = + owaspUtils.toBN( joReferenceLogRecord.currentMessage.toString() ); + if( bnCurrentMessage.eq( bnMessageNumberToFind ) ) { + if( isVerbose ) { + details.success( "{p}Found {} log record {} for message {}", + strLogPrefix, strEventName, joReferenceLogRecord, nMessageNumberToFind ); + } + return joReferenceLogRecord; + } + } + if( isVerbose ) { + details.error( "{p}Failed to find {} log record for message {}", strLogPrefix, + strEventName, nMessageNumberToFind ); + } + return null; +} + +async function findOutAllReferenceLogRecords( + details: log.TLogger, strLogPrefix: string, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joMessageProxy: owaspUtils.ethersMod.ethers.Contract, + bnBlockId: any, nIncMsgCnt: number, nOutMsgCnt: number, isVerbose?: boolean +): Promise { + if( isVerbose ) { + details.debug( + "{p}Optimized IMA message search algorithm will start at block {}" + + ", will search for outgoing message counter {} and approach down to incoming " + + "message counter {}", strLogPrefix, bnBlockId.toString(), + nOutMsgCnt.toString(), nIncMsgCnt.toString() ); + } + const arrLogRecordReferences: any = []; + const cntExpected = nOutMsgCnt - nIncMsgCnt; + if( cntExpected <= 0 ) { + if( isVerbose ) { + details.success( "{p}Optimized IMA message search algorithm success, " + + "nothing to search, result is empty", strLogPrefix ); + } + return arrLogRecordReferences; // nothing to search + } + let nWalkMsgNumber = nOutMsgCnt - 1; + let nWalkBlockId = bnBlockId; + for( ; nWalkMsgNumber >= nIncMsgCnt; --nWalkMsgNumber ) { + const joReferenceLogRecord = await findOutReferenceLogRecord( details, strLogPrefix, + ethersProvider, joMessageProxy, nWalkBlockId, nWalkMsgNumber, isVerbose ); + if( joReferenceLogRecord == null ) + break; + nWalkBlockId = owaspUtils.toBN( joReferenceLogRecord.previousOutgoingMessageBlockId ); + arrLogRecordReferences.unshift( joReferenceLogRecord ); + } + const cntFound = arrLogRecordReferences.length; + if( cntFound != cntExpected ) { + if( isVerbose ) { + details.error( + "{p}Optimized IMA message search algorithm fail, found {} log " + + "record(s), expected {} log record(s), found records are: {}", + strLogPrefix, cntFound, cntExpected, arrLogRecordReferences ); + } + } else { + if( isVerbose ) { + details.success( "{p}Optimized IMA message search algorithm success, found all {}" + + " log record(s): {}", strLogPrefix, cntFound, arrLogRecordReferences ); + } + } + return arrLogRecordReferences; +} + +let gTransferLoopCounter = 0; + +// Do real money movement from main-net to S-chain by sniffing events +// 1) main-net.MessageProxyForMainnet.getOutgoingMessagesCounter -> save to nOutMsgCnt +// 2) S-chain.MessageProxySchain.getIncomingMessagesCounter -> save to nIncMsgCnt +// 3) Will transfer all in range from [ nIncMsgCnt ... (nOutMsgCnt-1) ] ... +// assume current counter index is nIdxCurrentMsg +// +// One transaction transfer is: +// 1) Find events main-net.MessageProxyForMainnet.OutgoingMessage +// where msgCounter member is in range +// 2) Publish it to S-chain.MessageProxySchain.postIncomingMessages( +// main-net chain id // uint64 srcChainID +// nIdxCurrentMsg // uint64 startingCounter +// [srcContract] // address[] memory senders +// [dstContract] // address[] memory dstContracts +// [to] // address[] memory to +// [amount] // uint256[] memory amount / *uint256[2] memory blsSignature* / +// ) +async function doQueryOutgoingMessageCounter( optsTransfer: TTransferOptions ): Promise { + let nPossibleIntegerValue = 0; + optsTransfer.details.debug( "{p}SRC MessageProxy address is.....{}", + optsTransfer.strLogPrefixShort, optsTransfer.joMessageProxySrc.address ); + optsTransfer.details.debug( "{p}DST MessageProxy address is.....", + optsTransfer.strLogPrefixShort, optsTransfer.joMessageProxyDst.address ); + optsTransfer.strActionName = "src-chain.MessageProxy.getOutgoingMessagesCounter()"; + try { + optsTransfer.details.debug( "{p}Will call {bright}...", + optsTransfer.strLogPrefix, optsTransfer.strActionName ); + nPossibleIntegerValue = + await optsTransfer.joMessageProxySrc.callStatic.getOutgoingMessagesCounter( + optsTransfer.chainNameDst, + { from: optsTransfer.joAccountSrc.address() } ); + if( !owaspUtils.validateInteger( nPossibleIntegerValue ) ) { + throw new Error( `DST chain ${optsTransfer.chainNameDst} returned outgoing ` + + `message counter ${nPossibleIntegerValue} which is not a valid integer` ); + } + optsTransfer.nOutMsgCnt = owaspUtils.toInteger( nPossibleIntegerValue ); + optsTransfer.details.information( "{p}Result of {bright} call: {}", + optsTransfer.strLogPrefix, optsTransfer.strActionName, optsTransfer.nOutMsgCnt ); + } catch ( err ) { + optsTransfer.details.critical( + "(IMMEDIATE) error caught during {bright}, error details: {err}, stack is:\n{stack}", + optsTransfer.strActionName, err, err ); + } + + optsTransfer.strActionName = "dst-chain.MessageProxy.getIncomingMessagesCounter()"; + optsTransfer.details.debug( "{p}Will call {bright}...", + optsTransfer.strLogPrefix, optsTransfer.strActionName ); + nPossibleIntegerValue = + await optsTransfer.joMessageProxyDst.callStatic.getIncomingMessagesCounter( + optsTransfer.chainNameSrc, { from: optsTransfer.joAccountDst.address() } ); + if( !owaspUtils.validateInteger( nPossibleIntegerValue ) ) { + throw new Error( `SRC chain ${optsTransfer.chainNameSrc} returned incoming message ` + + `counter ${nPossibleIntegerValue} which is not a valid integer` ); + } + optsTransfer.nIncMsgCnt = owaspUtils.toInteger( nPossibleIntegerValue ); + optsTransfer.details.debug( "{p}Result of {bright} call: {}", + optsTransfer.strLogPrefix, optsTransfer.strActionName, optsTransfer.nIncMsgCnt ); + optsTransfer.strActionName = "src-chain.MessageProxy.getIncomingMessagesCounter()"; + nPossibleIntegerValue = + await optsTransfer.joMessageProxySrc.callStatic.getIncomingMessagesCounter( + optsTransfer.chainNameDst, { from: optsTransfer.joAccountSrc.address() } ); + if( !owaspUtils.validateInteger( nPossibleIntegerValue ) ) { + throw new Error( `DST chain ${optsTransfer.chainNameDst} returned incoming ` + + `message counter ${nPossibleIntegerValue} + which is not a valid integer` ); + } + const idxLastToPopNotIncluding = owaspUtils.toInteger( nPossibleIntegerValue ); + optsTransfer.details.debug( "{p}Result of {bright} call: {}", + optsTransfer.strLogPrefix, optsTransfer.strActionName, idxLastToPopNotIncluding ); + // first, try optimized scanner + optsTransfer.arrLogRecordReferences = []; + try { + optsTransfer.strActionName = + "in-getOutgoingMessagesCounter()--joMessageProxySrc.getLastOutgoingMessageBlockId()"; + const bnBlockId = + owaspUtils.toBN( + await optsTransfer.joMessageProxySrc.callStatic.getLastOutgoingMessageBlockId( + optsTransfer.chainNameDst, + { from: optsTransfer.joAccountSrc.address() } ) ); + try { + if( bnBlockId ) { + optsTransfer.strActionName = + "in-getOutgoingMessagesCounter()--findOutAllReferenceLogRecords()"; + optsTransfer.arrLogRecordReferences = + await findOutAllReferenceLogRecords( optsTransfer.details, + optsTransfer.strLogPrefixShort, optsTransfer.ethersProviderSrc, + optsTransfer.joMessageProxySrc, bnBlockId, optsTransfer.nIncMsgCnt, + optsTransfer.nOutMsgCnt, true ); + return true; // success, finish at this point + } + } catch ( err ) { + optsTransfer.arrLogRecordReferences = []; + optsTransfer.details.error( + "{p}Optimized log search is off. Running old IMA smart contracts? " + + "Please upgrade, if possible. Error is: {err}, stack is:\n{stack}", + optsTransfer.strLogPrefix, err, err ); + } + } catch ( err ) { + optsTransfer.arrLogRecordReferences = []; + optsTransfer.details.error( "{p}Optimized log search is un-available.", + optsTransfer.strLogPrefix ); + } + // second, use classic raw events search + optsTransfer.strActionName = "in-getOutgoingMessagesCounter()--classic-records-scanner"; + const attempts = 10; + const strEventName = "OutgoingMessage"; + const nBlockFrom = 0; + const nBlockTo = "latest"; + for( let nWalkMsgNumber = optsTransfer.nIncMsgCnt; + nWalkMsgNumber < optsTransfer.nOutMsgCnt; + ++nWalkMsgNumber + ) { + const joFilter = optsTransfer.joMessageProxySrc.filters[strEventName]( + owaspUtils.ethersMod.ethers.utils.id( optsTransfer.chainNameDst ), + owaspUtils.toBN( nWalkMsgNumber ) ); + const arrLogRecordReferencesWalk = + await imaEventLogScan.safeGetPastEventsProgressive( optsTransfer.details, + optsTransfer.strLogPrefixShort, optsTransfer.ethersProviderSrc, attempts, + optsTransfer.joMessageProxySrc, strEventName, nBlockFrom, nBlockTo, joFilter ); + optsTransfer.arrLogRecordReferences = + optsTransfer.arrLogRecordReferences.concat( arrLogRecordReferencesWalk ); + } + + return true; +} + +async function analyzeGatheredRecords( optsTransfer: TTransferOptions, r: any ): Promise { + let joValues: any = null; + const strChainHashWeAreLookingFor = + owaspUtils.ethersMod.ethers.utils.id( optsTransfer.chainNameDst ); + optsTransfer.details.debug( + "{p}Will review {} found event records(in reverse order, newest to oldest) while looking " + + "for hash {} of destination chain {}", optsTransfer.strLogPrefix, r.length, + strChainHashWeAreLookingFor, optsTransfer.chainNameDst ); + for( let i = r.length - 1; i >= 0; i-- ) { + const joEvent = r[i]; + optsTransfer.details.debug( "{p}Will review found event record {} with data {}", + optsTransfer.strLogPrefix, i, joEvent ); + const ev = { + dstChainHash: joEvent.args[0], + msgCounter: joEvent.args[1], + srcContract: joEvent.args[2], + dstContract: joEvent.args[3], + data: joEvent.args[4] + }; + if( ev.dstChainHash == strChainHashWeAreLookingFor ) { + joValues = ev; + joValues.savedBlockNumberForOptimizations = r[i].blockNumber; + optsTransfer.details.success( + "{p}Found event record {} reviewed and accepted for processing, found event " + + "values are {}, found block number is {}", optsTransfer.strLogPrefix, i, joValues, + joValues.savedBlockNumberForOptimizations ); + break; + } else { + optsTransfer.details.debug( "{p}Found event record {} reviewed and skipped", + optsTransfer.strLogPrefix, i ); + } + } + if( joValues == "" ) { + optsTransfer.details.critical( "{p}Can't get events from MessageProxy", + optsTransfer.strLogPrefix ); + optsTransfer.details.exposeDetailsTo( + log.globalStream(), optsTransfer.strGatheredDetailsName, false ); + imaTransferErrorHandling.saveTransferError( + optsTransfer.strTransferErrorCategoryName, optsTransfer.details.toString() ); + optsTransfer.details.close(); + return null; // caller will return false if we return null here + } + return joValues; +} + +async function gatherMessages( optsTransfer: TTransferOptions ): Promise { + optsTransfer.arrMessageCounters = []; + optsTransfer.jarrMessages = []; + optsTransfer.nIdxCurrentMsgBlockStart = owaspUtils.toInteger( optsTransfer.nIdxCurrentMsg ); + let r; + optsTransfer.cntAccumulatedForBlock = 0; + for( let idxInBlock = 0; // inner loop wil create block of transactions + optsTransfer.nIdxCurrentMsg < optsTransfer.nOutMsgCnt && + idxInBlock < optsTransfer.nTransactionsCountInBlock; + ++optsTransfer.nIdxCurrentMsg, ++idxInBlock, ++optsTransfer.cntAccumulatedForBlock + ) { + const idxProcessing = optsTransfer.cntProcessed + idxInBlock; + if( idxProcessing > optsTransfer.nMaxTransactionsCount ) + break; + let nBlockFrom = 0; let nBlockTo = "latest"; + if( optsTransfer.arrLogRecordReferences.length > 0 ) { + const joReferenceLogRecord = optsTransfer.arrLogRecordReferences.shift(); + if( joReferenceLogRecord && "currentBlockId" in joReferenceLogRecord && + joReferenceLogRecord.currentBlockId ) { + nBlockFrom = joReferenceLogRecord.currentBlockId; + nBlockTo = joReferenceLogRecord.currentBlockId; + } + } + optsTransfer.strActionName = "src-chain->MessageProxy->scan-past-events()"; + const strEventName = "OutgoingMessage"; + optsTransfer.details.debug( "{p}Will call {bright} for {} event...", + optsTransfer.strLogPrefix, optsTransfer.strActionName, strEventName ); + r = await imaEventLogScan.safeGetPastEventsProgressive( optsTransfer.details, + optsTransfer.strLogPrefixShort, optsTransfer.ethersProviderSrc, 10, + optsTransfer.joMessageProxySrc, strEventName, nBlockFrom, nBlockTo, + optsTransfer.joMessageProxySrc.filters[strEventName]( + owaspUtils.ethersMod.ethers.utils.id( optsTransfer.chainNameDst ), + owaspUtils.toBN( optsTransfer.nIdxCurrentMsg ) ) ); + const joValues = await analyzeGatheredRecords( optsTransfer, r ); + if( joValues == null ) + return false; + if( optsTransfer.nBlockAwaitDepth > 0 ) { + let bSecurityCheckPassed = true; + const strActionNameOld = optsTransfer.strActionName; + optsTransfer.strActionName = "security check: evaluate block depth"; + try { + const transactionHash = r[0].transactionHash; + optsTransfer.details.debug( "{p}Event transactionHash is {}", + optsTransfer.strLogPrefix, transactionHash ); + const blockNumber = r[0].blockNumber; + optsTransfer.details.debug( "{p}Event blockNumber is {}", + optsTransfer.strLogPrefix, blockNumber ); + const nLatestBlockNumber = await imaHelperAPIs.safeGetBlockNumber( + optsTransfer.details, 10, optsTransfer.ethersProviderSrc ); + optsTransfer.details.debug( "{p}Latest blockNumber is {}", + optsTransfer.strLogPrefix, nLatestBlockNumber ); + const nDist = nLatestBlockNumber - blockNumber; + if( nDist < optsTransfer.nBlockAwaitDepth ) + bSecurityCheckPassed = false; + optsTransfer.details.debug( "{p}Distance by blockNumber is {}, await check is {}", + optsTransfer.strLogPrefix, nDist, + log.posNeg( bSecurityCheckPassed, "PASSED", "FAILED" ) ); + } catch ( err ) { + bSecurityCheckPassed = false; + optsTransfer.details.critical( + "{p}Exception(evaluate block depth) while getting transaction hash and " + + "block number during {bright}: {err}, stack is:\n{stack}", + optsTransfer.strLogPrefix, optsTransfer.strActionName, err, err ); + optsTransfer.details.exposeDetailsTo( + log.globalStream(), optsTransfer.strGatheredDetailsName, false ); + imaTransferErrorHandling.saveTransferError( + optsTransfer.strTransferErrorCategoryName, optsTransfer.details.toString() ); + optsTransfer.details.close(); + return false; + } + optsTransfer.strActionName = strActionNameOld.toString(); + if( !bSecurityCheckPassed ) { + optsTransfer.details.warning( "{p}Block depth check was not passed, canceling " + + "search for transfer events", optsTransfer.strLogPrefix ); + break; + } + } + if( optsTransfer.nBlockAge > 0 ) { + let bSecurityCheckPassed = true; + const strActionNameOld = optsTransfer.strActionName; + optsTransfer.strActionName = "security check: evaluate block age"; + try { + const transactionHash = r[0].transactionHash; + optsTransfer.details.debug( "{p}Event transactionHash is {}", + optsTransfer.strLogPrefix, transactionHash ); + const blockNumber = r[0].blockNumber; + optsTransfer.details.debug( "{p}Event blockNumber is {}", + optsTransfer.strLogPrefix, blockNumber ); + const joBlock = await optsTransfer.ethersProviderSrc.getBlock( blockNumber ); + if( !owaspUtils.validateInteger( joBlock.timestamp ) ) { + throw new Error( "Block timestamp is not a valid " + + `integer value: ${joBlock.timestamp}` ); + } + const timestampBlock = owaspUtils.toInteger( joBlock.timestamp ); + optsTransfer.details.debug( "{p}Block TS is {}", + optsTransfer.strLogPrefix, timestampBlock ); + const timestampCurrent = imaHelperAPIs.currentTimestamp(); + optsTransfer.details.debug( "{p}Current TS is {}", + optsTransfer.strLogPrefix, timestampCurrent ); + const tsDiff = timestampCurrent - timestampBlock; + optsTransfer.details.debug( "{p}Diff TS is {}", + optsTransfer.strLogPrefix, tsDiff ); + optsTransfer.details.debug( "{p}Expected diff {}", + optsTransfer.strLogPrefix, optsTransfer.nBlockAge ); + if( tsDiff < optsTransfer.nBlockAge ) + bSecurityCheckPassed = false; + optsTransfer.details.debug( "{p}Block age check is {}", + optsTransfer.strLogPrefix, + log.posNeg( bSecurityCheckPassed, "PASSED", "FAILED" ) ); + } catch ( err ) { + bSecurityCheckPassed = false; + optsTransfer.details.critical( + "{p}Exception(evaluate block age) while getting block number and timestamp " + + "during {bright}: {err}, stack is:\n{stack}", optsTransfer.strLogPrefix, + optsTransfer.strActionName, err, err ); + optsTransfer.details.exposeDetailsTo( + log.globalStream(), optsTransfer.strGatheredDetailsName, false ); + imaTransferErrorHandling.saveTransferError( + optsTransfer.strTransferErrorCategoryName, optsTransfer.details.toString() ); + optsTransfer.details.close(); + return false; + } + optsTransfer.strActionName = strActionNameOld.toString(); + if( !bSecurityCheckPassed ) { + optsTransfer.details.warning( "{p}Block age check was not passed, " + + "canceling search for transfer events", optsTransfer.strLogPrefix ); + break; + } + } + optsTransfer.details.success( + "{p}Got event details from getPastEvents() event invoked with msgCounter set to {} " + + "and dstChain set to {}, event description: ", optsTransfer.strLogPrefix, + optsTransfer.nIdxCurrentMsg, optsTransfer.chainNameDst, joValues ); + optsTransfer.details.debug( "{p}Will process message counter value {}", + optsTransfer.strLogPrefix, optsTransfer.nIdxCurrentMsg ); + optsTransfer.arrMessageCounters.push( optsTransfer.nIdxCurrentMsg ); + const joMessage: any = { + sender: joValues.srcContract, + destinationContract: joValues.dstContract, + to: joValues.to, + amount: joValues.amount, + data: joValues.data, + savedBlockNumberForOptimizations: joValues.savedBlockNumberForOptimizations + }; + optsTransfer.jarrMessages.push( joMessage ); + } + return true; +} + +async function preCheckAllMessagesSign( + optsTransfer: TTransferOptions, err: Error | string | null, + jarrMessages: any[], joGlueResult: any ): Promise { + const strDidInvokedSigningCallbackMessage = log.fmtDebug( + "{p}Did invoked message signing callback, first real message index is: {}, have {} " + + "message(s) to process {}", optsTransfer.strLogPrefix, + optsTransfer.nIdxCurrentMsgBlockStart, optsTransfer.jarrMessages.length, + optsTransfer.jarrMessages ); + optsTransfer.details.debug( strDidInvokedSigningCallbackMessage ); + if( err ) { + optsTransfer.bErrorInSigningMessages = true; + optsTransfer.details.critical( "{p}Error signing messages: {err}", + optsTransfer.strLogPrefix, err ); + imaTransferErrorHandling.saveTransferError( + optsTransfer.strTransferErrorCategoryName, optsTransfer.details.toString() ); + return false; + } + if( !loop.checkTimeFraming( null, optsTransfer.strDirection, optsTransfer.joRuntimeOpts ) ) { + optsTransfer.details.warning( "{p}Time framing overflow (after signing messages)", + optsTransfer.strLogPrefix ); + imaTransferErrorHandling.saveTransferSuccessAll(); + return false; + } + return true; +} + +async function callbackAllMessagesSign( + optsTransfer: TTransferOptions, err: Error | string | null, + jarrMessages: any[], joGlueResult: any ): Promise { + if( !await preCheckAllMessagesSign( optsTransfer, err, jarrMessages, joGlueResult ) ) + return; + const nBlockSize = optsTransfer.arrMessageCounters.length; + optsTransfer.strActionName = "dst-chain.MessageProxy.postIncomingMessages()"; + const strWillCallPostIncomingMessagesAction = log.fmtDebug( + "{p}Will call {bright} for block size set to {}, message counters = are {}...", + optsTransfer.strLogPrefix, optsTransfer.strActionName, + nBlockSize, optsTransfer.arrMessageCounters ); + optsTransfer.details.debug( strWillCallPostIncomingMessagesAction ); + let signature: owaspUtils.TXYSignature | null = joGlueResult ? joGlueResult.signature : null; + if( !signature ) + signature = { X: "0", Y: "0" }; + let hashPoint = joGlueResult ? joGlueResult.hashPoint : null; + if( !hashPoint ) + hashPoint = { X: "0", Y: "0" }; + let hint = joGlueResult ? joGlueResult.hint : null; + if( !hint ) + hint = "0"; + const sign: owaspUtils.TBLSSignature = { + blsSignature: [ signature.X, signature.Y ], // BLS glue of signatures + hashA: hashPoint.X, // G1.X from joGlueResult.hashSrc + hashB: hashPoint.Y, // G1.Y from joGlueResult.hashSrc + counter: hint + }; + const arrArgumentsPostIncomingMessages = [ + optsTransfer.chainNameSrc, optsTransfer.nIdxCurrentMsgBlockStart, + optsTransfer.jarrMessages, sign ]; + const joDebugArgs = [ + optsTransfer.chainNameSrc, optsTransfer.chainNameDst, + optsTransfer.nIdxCurrentMsgBlockStart, + optsTransfer.jarrMessages, [ signature.X, signature.Y ], // BLS glue of signatures + hashPoint.X, // G1.X from joGlueResult.hashSrc + hashPoint.Y, // G1.Y from joGlueResult.hashSrc + hint ]; + optsTransfer.details.debug( "{p}....debug args for msgCounter set to {}: {}", + optsTransfer.strLogPrefix, optsTransfer.nIdxCurrentMsgBlockStart, joDebugArgs ); + optsTransfer.strActionName = optsTransfer.strDirection + " - Post incoming messages"; + const weiHowMuchPostIncomingMessages = undefined; + const gasPrice = await optsTransfer.transactionCustomizerDst.computeGasPrice( + optsTransfer.ethersProviderDst, 200000000000 ); + optsTransfer.details.debug( "{p}Using computed gasPrice {}={}", + optsTransfer.strLogPrefix, gasPrice ); + let estimatedGasPostIncomingMessages = + await optsTransfer.transactionCustomizerDst.computeGas( + optsTransfer.details, optsTransfer.ethersProviderDst, + "MessageProxy", optsTransfer.joMessageProxyDst, + "postIncomingMessages", arrArgumentsPostIncomingMessages, + optsTransfer.joAccountDst, optsTransfer.strActionName, + gasPrice, 10000000, weiHowMuchPostIncomingMessages, null ); + optsTransfer.details.debug( "{p}Using estimated gas={}", + optsTransfer.strLogPrefix, estimatedGasPostIncomingMessages ); + if( optsTransfer.strDirection == "S2M" ) { + const expectedGasLimit = perMessageGasForTransfer * optsTransfer.jarrMessages.length + + additionalS2MTransferOverhead; + estimatedGasPostIncomingMessages = + Math.max( estimatedGasPostIncomingMessages, expectedGasLimit ); + } + const isIgnorePostIncomingMessages = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + optsTransfer.details, optsTransfer.ethersProviderDst, + "MessageProxy", optsTransfer.joMessageProxyDst, + "postIncomingMessages", arrArgumentsPostIncomingMessages, + optsTransfer.joAccountDst, optsTransfer.strActionName, + isIgnorePostIncomingMessages, + gasPrice, estimatedGasPostIncomingMessages, + weiHowMuchPostIncomingMessages, null ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + const opts: imaTx.TCustomPayedCallOptions = { + isCheckTransactionToSchain: + ( optsTransfer.chainNameDst !== "Mainnet" ) + }; + const joReceipt = await imaTx.payedCall( + optsTransfer.details, optsTransfer.ethersProviderDst, + "MessageProxy", optsTransfer.joMessageProxyDst, + "postIncomingMessages", arrArgumentsPostIncomingMessages, + optsTransfer.joAccountDst, optsTransfer.strActionName, + gasPrice, estimatedGasPostIncomingMessages, + weiHowMuchPostIncomingMessages, opts ); + if( joReceipt ) { + optsTransfer.jarrReceipts.push( { + description: "doTransfer/postIncomingMessages()", + "optsTransfer.detailsString": optsTransfer.strGatheredDetailsName, + receipt: joReceipt + } ); + imaGasUsage.printGasUsageReportFromArray( "(intermediate result) TRANSFER " + + optsTransfer.chainNameSrc + " -> " + optsTransfer.chainNameDst, + optsTransfer.jarrReceipts, optsTransfer.details ); + } + optsTransfer.cntProcessed += optsTransfer.cntAccumulatedForBlock; + optsTransfer.details.information( "{p}Validating transfer from {} to {}...", + optsTransfer.strLogPrefix, optsTransfer.chainNameSrc, optsTransfer.chainNameDst ); + // check DepositBox -> Error on Mainnet only + if( optsTransfer.chainNameDst == "Mainnet" ) { + optsTransfer.details.debug( "{p}Validating transfer to Main Net via MessageProxy error " + + "absence on Main Net...", optsTransfer.strLogPrefix ); + if( optsTransfer.joDepositBoxMainNet ) { + if( joReceipt && "blockNumber" in joReceipt && "transactionHash" in joReceipt ) { + const strEventName = "PostMessageError"; + optsTransfer.details.debug( + "{p}Verifying the {} event of the MessageProxy/{} contract...", + optsTransfer.strLogPrefix, strEventName, + optsTransfer.joMessageProxyDst.address ); + const joEvents = await imaEventLogScan.getContractCallEvents( + optsTransfer.details, optsTransfer.strLogPrefixShort, + optsTransfer.ethersProviderDst, optsTransfer.joMessageProxyDst, strEventName, + joReceipt.blockNumber, joReceipt.transactionHash, + optsTransfer.joMessageProxyDst.filters[strEventName]() ); + if( joEvents.length == 0 ) { + optsTransfer.details.success( + "{p}Success, verified the {} event of the MessageProxy/{} contract, " + + "no events found", optsTransfer.strLogPrefix, strEventName, + optsTransfer.joMessageProxyDst.address ); + } else { + optsTransfer.details.critical( + "{p}Failed verification of the PostMessageError event of the " + + "MessageProxy/{} contract, found event(s): {}", optsTransfer.strLogPrefix, + optsTransfer.joMessageProxyDst.address, joEvents ); + imaTransferErrorHandling.saveTransferError( + optsTransfer.strTransferErrorCategoryName, + optsTransfer.details.toString() ); + throw new Error( "Verification failed for the PostMessageError event " + + `of the MessageProxy ${optsTransfer.joMessageProxyDst.address} ` + + "contract, error events found" ); + } + optsTransfer.details.success( "{p}Done, validated transfer to Main Net via " + + "MessageProxy error absence on Main Net", optsTransfer.strLogPrefix ); + } else { + optsTransfer.details.warning( "{p}Cannot validate transfer to Main Net via " + + "MessageProxy error absence on Main Net, no valid transaction receipt provided", + optsTransfer.strLogPrefix ); + } + } else { + optsTransfer.details.warning( "{p}Cannot validate transfer to Main Net via " + + "MessageProxy error absence on Main Net, no MessageProxy provided", + optsTransfer.strLogPrefix ); + } + } +} + +async function handleAllMessagesSigning( optsTransfer: TTransferOptions ): Promise { + try { + let strErrFinal: string = ""; + await optsTransfer.fnSignMessages( optsTransfer.nTransferLoopCounter, + optsTransfer.jarrMessages, optsTransfer.nIdxCurrentMsgBlockStart, + optsTransfer.chainNameSrc, optsTransfer.joExtraSignOpts, + async function( err: Error | string | null, jarrMessages: any[], joGlueResult: any ) { + await callbackAllMessagesSign( optsTransfer, err, jarrMessages, joGlueResult ); + } ).catch( function( err: Error | string ): void { + // callback fn as argument of optsTransfer.fnSignMessages + optsTransfer.bErrorInSigningMessages = true; + optsTransfer.details.error( "{p}Problem in transfer handler(in signer): {err}", + optsTransfer.strLogPrefix, err ); + imaTransferErrorHandling.saveTransferError( + optsTransfer.strTransferErrorCategoryName, optsTransfer.details.toString() ); + strErrFinal = err.toString(); + } ); + if( strErrFinal ) + throw new Error( strErrFinal ); + return true; + } catch ( err ) { + optsTransfer.details.error( "{p}Problem in transfer handler(general): {err}", + optsTransfer.strLogPrefix, err ); + imaTransferErrorHandling.saveTransferError( optsTransfer.strTransferErrorCategoryName, + optsTransfer.details.toString() ); + return false; + } +} + +async function checkOutgoingMessageEventInOneNode( + optsTransfer: TTransferOptions, + optsOutgoingMessageAnalysis: TOutgoingMessageAnalysisOptions +): Promise { + if( !optsOutgoingMessageAnalysis.joNode ) { + optsTransfer.details.error( + "{p}{bright} no S-Chain node provided", + optsTransfer.strLogPrefix, optsTransfer.strDirection ); + return false; + } + const sc = optsTransfer.imaState.chainProperties.sc; + const strUrlHttp = optsOutgoingMessageAnalysis.joNode.endpoints.ip.http; + optsTransfer.details.trace( + "{p}Validating {bright} message {} on node {} using URL {url}...", + optsTransfer.strLogPrefix, optsTransfer.strDirection, + optsOutgoingMessageAnalysis.idxMessage + 1, + optsOutgoingMessageAnalysis.joNode.name, strUrlHttp ); + const joMessage = optsOutgoingMessageAnalysis.joMessage; + let bEventIsFound = false; + try { + const ethersProviderNode = owaspUtils.getEthersProviderFromURL( strUrlHttp ); + const joMessageProxyNode: owaspUtils.ethersMod.ethers.Contract = + new owaspUtils.ethersMod.ethers.Contract( + sc.joAbiIMA.message_proxy_chain_address, + sc.joAbiIMA.message_proxy_chain_abi, + ethersProviderNode ); + const strEventName = "OutgoingMessage"; + const nodeRV = await imaEventLogScan.safeGetPastEventsProgressive( + optsTransfer.details, optsTransfer.strLogPrefixShort, ethersProviderNode, + 10, joMessageProxyNode, strEventName, + joMessage.savedBlockNumberForOptimizations, + joMessage.savedBlockNumberForOptimizations, + joMessageProxyNode.filters[strEventName]( + owaspUtils.ethersMod.ethers.utils.id( optsTransfer.chainNameDst ), + owaspUtils.toBN( optsOutgoingMessageAnalysis.idxImaMessage ) ) ); + const cntEvents = nodeRV.length; + optsTransfer.details.trace( + "{p}Got {} event(s) ({}) on node {} with data: {}", + optsTransfer.strLogPrefix, cntEvents, strEventName, + optsOutgoingMessageAnalysis.joNode.name, nodeRV ); + for( let idxEvent = 0; idxEvent < cntEvents; ++idxEvent ) { + const joEvent = nodeRV[idxEvent]; + const eventValuesByName: any = { + dstChainHash: joEvent.args[0], + msgCounter: joEvent.args[1], + srcContract: joEvent.args[2], + dstContract: joEvent.args[3], + data: joEvent.args[4] + }; + if( owaspUtils.ensureStartsWith0x( joMessage.sender ).toLowerCase() == + owaspUtils.ensureStartsWith0x( eventValuesByName.srcContract ).toLowerCase() && + owaspUtils.ensureStartsWith0x( joMessage.destinationContract ).toLowerCase() == + owaspUtils.ensureStartsWith0x( eventValuesByName.dstContract ).toLowerCase() + ) { + bEventIsFound = true; + break; + } + } + } catch ( err ) { + ++optsOutgoingMessageAnalysis.cntFailedNodes; + optsTransfer.details.error( + "{p}{bright} message analysis error: Failed to scan events on node {}, " + + "detailed node description is: {}, error is: {err}, stack is: ", + optsTransfer.strLogPrefix, optsTransfer.strDirection, + optsOutgoingMessageAnalysis.joNode.name, optsOutgoingMessageAnalysis.joNode, + err, err ); + return true; // continue nodes analysis + } + if( bEventIsFound ) { + ++optsOutgoingMessageAnalysis.cntPassedNodes; + optsTransfer.details.success( + "{p}{bright} message {} validation on node {} using URL {url} is passed", + optsTransfer.strLogPrefix, optsTransfer.strDirection, + optsOutgoingMessageAnalysis.idxMessage + 1, + optsOutgoingMessageAnalysis.joNode.name, strUrlHttp ); + } else { + ++optsOutgoingMessageAnalysis.cntFailedNodes; + optsTransfer.details.error( + "{p}{bright} message {} validation on node {} using URL {url} is failed", + optsTransfer.strLogPrefix, optsTransfer.strDirection, + optsOutgoingMessageAnalysis.idxMessage + 1, + optsOutgoingMessageAnalysis.joNode.name, strUrlHttp ); + } + if( optsOutgoingMessageAnalysis.cntFailedNodes > optsTransfer.cntNodesMayFail ) + return false; + if( optsOutgoingMessageAnalysis.cntPassedNodes >= optsTransfer.cntNodesShouldPass ) { + optsTransfer.details.information( + "{p}{bright} message {} validation on node {} using URL {url} is passed", + optsTransfer.strLogPrefix, optsTransfer.strDirection, + optsOutgoingMessageAnalysis.idxMessage + 1, + optsOutgoingMessageAnalysis.joNode.name, strUrlHttp ); + return false; + } + return true; +} + +async function checkOutgoingMessageEvent( + optsTransfer: TTransferOptions, joSChain: skaleObserver.TSChainInformation ): Promise { + const cntNodes = joSChain.nodes.length; + const cntMessages = optsTransfer.jarrMessages.length; + for( let idxMessage = 0; idxMessage < cntMessages; ++idxMessage ) { + const idxImaMessage = optsTransfer.arrMessageCounters[idxMessage]; + const joMessage = optsTransfer.jarrMessages[idxMessage]; + optsTransfer.details.trace( + "{p}{bright} message analysis for message {} of {} with IMA message index {} and " + + "message envelope data: {}", optsTransfer.strLogPrefix, optsTransfer.strDirection, + idxMessage + 1, cntMessages, idxImaMessage, joMessage ); + const optsOutgoingMessageAnalysis: TOutgoingMessageAnalysisOptions = { + idxMessage, + idxImaMessage, + joMessage, + joNode: null, + idxNode: 0, + cntNodes, + cntPassedNodes: 0, + cntFailedNodes: 0 + }; + try { + for( optsOutgoingMessageAnalysis.idxNode = 0; + optsOutgoingMessageAnalysis.idxNode < cntNodes; + ++optsOutgoingMessageAnalysis.idxNode + ) { + optsOutgoingMessageAnalysis.joNode = joSChain.nodes[ + optsOutgoingMessageAnalysis.idxNode]; + const isContinueNodesAnalysis = await checkOutgoingMessageEventInOneNode( + optsTransfer, optsOutgoingMessageAnalysis ); + if( !isContinueNodesAnalysis ) + break; + } + } catch ( err ) { + const strUrlHttp = optsOutgoingMessageAnalysis.joNode + ? optsOutgoingMessageAnalysis.joNode.endpoints.ip.http + : ""; + const strNodeName = optsOutgoingMessageAnalysis.joNode + ? log.fmtInformation( optsOutgoingMessageAnalysis.joNode.name ) + : log.fmtError( "<>" ); + optsTransfer.details.critical( + "{p}{bright} message analysis error: Failed to process events for {} message {} " + + "on node {} using URL {}, error is: {err}, stack is:\n{stack}", + optsTransfer.strLogPrefix, optsTransfer.strDirection, optsTransfer.strDirection, + idxMessage + 1, strNodeName, + log.posNeg( optsOutgoingMessageAnalysis.joNode, + log.u( strUrlHttp ), "<>" ), + err, err ); + } + if( optsOutgoingMessageAnalysis.cntFailedNodes > optsTransfer.cntNodesMayFail ) { + optsTransfer.details.critical( + "{p}Error validating {bright} messages, failed node count {} is greater then " + + "allowed to fail {}", optsTransfer.strLogPrefix, optsTransfer.strDirection, + optsOutgoingMessageAnalysis.cntFailedNodes, optsTransfer.cntNodesMayFail ); + optsTransfer.details.exposeDetailsTo( + log.globalStream(), optsTransfer.strGatheredDetailsName, false ); + imaTransferErrorHandling.saveTransferError( + optsTransfer.strTransferErrorCategoryName, + optsTransfer.details.toString() ); + optsTransfer.details.close(); + return false; + } + if( !( optsOutgoingMessageAnalysis.cntPassedNodes >= optsTransfer.cntNodesShouldPass ) ) { + optsTransfer.details.critical( + "{p}Error validating {bright} messages, passed node count {} is less then " + + "needed count {}", optsTransfer.strLogPrefix, optsTransfer.strDirection, + optsOutgoingMessageAnalysis.cntFailedNodes, optsTransfer.cntNodesShouldPass ); + optsTransfer.details.exposeDetailsTo( + log.globalStream(), optsTransfer.strGatheredDetailsName, false ); + imaTransferErrorHandling.saveTransferError( + optsTransfer.strTransferErrorCategoryName, optsTransfer.details.toString() ); + optsTransfer.details.close(); + return false; + } + } + return true; +} + +async function doMainTransferLoopActions( optsTransfer: TTransferOptions ): Promise { + // classic scanner with optional usage of optimized IMA messages search algorithm + // outer loop is block former/creator, then transfer + optsTransfer.nIdxCurrentMsg = optsTransfer.nIncMsgCnt; + while( optsTransfer.nIdxCurrentMsg < optsTransfer.nOutMsgCnt ) { + if( optsTransfer.nStepsDone > optsTransfer.nTransferSteps ) { + optsTransfer.details.warning( "{p}Transfer step count overflow", + optsTransfer.strLogPrefix ); + optsTransfer.details.close(); + imaTransferErrorHandling.saveTransferSuccessAll(); + return false; + } + optsTransfer.details.trace( + "{p}Entering block former iteration with message counter set to {}, transfer step " + + "number is {}, can transfer up to {} message(s) per step, can perform up to {} " + + "transfer step(s)", optsTransfer.strLogPrefix, optsTransfer.nIdxCurrentMsg, + optsTransfer.nStepsDone, optsTransfer.nMaxTransactionsCount, + optsTransfer.nTransferSteps ); + if( !loop.checkTimeFraming( + null, optsTransfer.strDirection, optsTransfer.joRuntimeOpts ) ) { + optsTransfer.details.warning( "{p}WARNING: Time framing overflow" + + "(after entering block former iteration loop)", optsTransfer.strLogPrefix ); + optsTransfer.details.close(); + imaTransferErrorHandling.saveTransferSuccessAll(); + return false; + } + await gatherMessages( optsTransfer ); + if( optsTransfer.cntAccumulatedForBlock == 0 ) + break; + if( !loop.checkTimeFraming( + null, optsTransfer.strDirection, optsTransfer.joRuntimeOpts ) ) { + optsTransfer.details.warning( "{p}Time framing overflow" + + "(after forming block of messages)", optsTransfer.strLogPrefix ); + optsTransfer.details.close(); + imaTransferErrorHandling.saveTransferSuccessAll(); + return false; + } + if( optsTransfer.strDirection == "S2S" ) { + optsTransfer.strActionName = "S2S message analysis"; + if( !optsTransfer.joExtraSignOpts ) { + throw new Error( "Could not validate S2S messages, " + + "no extra options provided to transfer algorithm" ); + } + const arrSChainsCached = skaleObserver.getLastCachedSChains(); + if( ( !arrSChainsCached ) || arrSChainsCached.length == 0 ) { + throw new Error( "Could not validate S2S messages, " + + "no S-Chains in SKALE NETWORK observer cached yet, try again later" ); + } + const idxSChain = skaleObserver.findSChainIndexInArrayByName( + arrSChainsCached, optsTransfer.chainNameSrc ); + if( idxSChain < 0 ) { + throw new Error( "Could not validate S2S messages, source " + + `S-Chain ${optsTransfer.chainNameSrc} is not in SKALE NETWORK observer ` + + `cache yet or it's not connected to this ${optsTransfer.chainNameDst} ` + + "S-Chain yet, try again later" ); + } + const cntMessages = optsTransfer.jarrMessages.length; + const joSChain = arrSChainsCached[idxSChain]; + const cntNodes = joSChain.nodes.length; + optsTransfer.cntNodesShouldPass = Math.ceil( ( cntNodes * 2 ) / 3 ); + optsTransfer.cntNodesMayFail = cntNodes - optsTransfer.cntNodesShouldPass; + optsTransfer.details.trace( + "{p}{bright} message analysis will be performed on S-Chain {} with {} node(s), " + + "{} node(s) should have same message(s), {} node(s) allowed to fail message(s) " + + "comparison, {} message(s) to check...", optsTransfer.strLogPrefix, + optsTransfer.strDirection, optsTransfer.chainNameSrc, cntNodes, + optsTransfer.cntNodesShouldPass, optsTransfer.cntNodesMayFail, cntMessages ); + if( !( await checkOutgoingMessageEvent( optsTransfer, joSChain ) ) ) + return false; + } + + optsTransfer.strActionName = "sign messages"; + const strWillInvokeSigningCallbackMessage = log.fmtDebug( + "{p}Will invoke message signing callback, first real message index is: {}, have {} " + + "message(s) to process {}", optsTransfer.strLogPrefix, + optsTransfer.nIdxCurrentMsgBlockStart, optsTransfer.jarrMessages.length, + optsTransfer.jarrMessages ); + optsTransfer.details.information( strWillInvokeSigningCallbackMessage ); + // will re-open optsTransfer.details B log here for next step, + // it can be delayed so we will flush accumulated optsTransfer.details A now + if( log.exposeDetailsGet() && optsTransfer.details.exposeDetailsTo ) { + optsTransfer.details.exposeDetailsTo( + log.globalStream(), optsTransfer.strGatheredDetailsName, true ); + } + + optsTransfer.details.close(); + optsTransfer.details = optsTransfer.imaState.isDynamicLogInDoTransfer + ? log.globalStream() + : log.createMemoryStream(); + optsTransfer.strGatheredDetailsName = `${optsTransfer.strDirection}/#` + + `${optsTransfer.nTransferLoopCounter}-doTransfer-B-${optsTransfer.chainNameSrc}` + + `-->${optsTransfer.chainNameDst}`; + try { + if( !( await handleAllMessagesSigning( optsTransfer ) ) ) + return false; + } catch ( err ) { + optsTransfer.details.critical( + "{p}Exception from signing messages function: {err}, stack is:\n{stack}", + optsTransfer.strLogPrefix, err, err ); + } + if( optsTransfer.bErrorInSigningMessages ) + break; + ++optsTransfer.nStepsDone; + } + return true; +} + +let gIsOneTransferInProgressInThisThread = false; + +export async function doTransfer( + strDirection: string, joRuntimeOpts: loop.TRuntimeOpts, + ethersProviderSrc: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joMessageProxySrc: owaspUtils.ethersMod.ethers.Contract, joAccountSrc: state.TAccount, + ethersProviderDst: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joMessageProxyDst: owaspUtils.ethersMod.ethers.Contract, joAccountDst: state.TAccount, + chainNameSrc: string, chainNameDst: string, chainIdSrc: string, chainIdDst: string, + joDepositBoxMainNet: + owaspUtils.ethersMod.ethers.Contract | null, // for logs validation on mainnet + joTokenManagerSChain: + owaspUtils.ethersMod.ethers.Contract | null, // for logs validation on s-chain + nTransactionsCountInBlock: number, + nTransferSteps: number, nMaxTransactionsCount: number, + nBlockAwaitDepth: number, nBlockAge: number, + fnSignMessages: TFunctionDoSignMessages, joExtraSignOpts: loop.TExtraSignOpts | null, + transactionCustomizerDst: imaTx.TransactionCustomizer +): Promise { + const optsTransfer: TTransferOptions = { + strDirection, + joRuntimeOpts, + ethersProviderSrc, + joMessageProxySrc, + joAccountSrc, + ethersProviderDst, + joMessageProxyDst, + joAccountDst, + chainNameSrc, + chainNameDst, + chainIdSrc, + chainIdDst, + joDepositBoxMainNet, // for logs validation on mainnet + joTokenManagerSChain, // for logs validation on s-chain + nTransactionsCountInBlock, + nTransferSteps, + nMaxTransactionsCount, + nBlockAwaitDepth, + nBlockAge, + fnSignMessages, + joExtraSignOpts, + transactionCustomizerDst, + imaState: state.get(), + nTransferLoopCounter: owaspUtils.toInteger( gTransferLoopCounter ), + strTransferErrorCategoryName: "loop-" + strDirection, + strGatheredDetailsName: "", + details: log.globalStream(), + jarrReceipts: [], + bErrorInSigningMessages: false, + strLogPrefixShort: "", + strLogPrefix: "", + nStepsDone: 0, + strActionName: "", + nIdxCurrentMsg: 0, + nOutMsgCnt: 0, + nIncMsgCnt: 0, + cntProcessed: 0, + arrMessageCounters: [], + jarrMessages: [], + nIdxCurrentMsgBlockStart: 0, + cntAccumulatedForBlock: 0, + arrLogRecordReferences: [], + cntNodesShouldPass: 0, + cntNodesMayFail: 0 + }; + ++gTransferLoopCounter; + optsTransfer.strGatheredDetailsName = + `${optsTransfer.strDirection}/#${optsTransfer.nTransferLoopCounter}-doTransfer-A-` + + `${optsTransfer.chainNameSrc}-->${optsTransfer.chainNameDst}`; + optsTransfer.details = optsTransfer.imaState.isDynamicLogInDoTransfer + ? log.globalStream() + : log.createMemoryStream(); + optsTransfer.strLogPrefixShort = + `${optsTransfer.strDirection}/#${optsTransfer.nTransferLoopCounter} `; + optsTransfer.strLogPrefix = `${optsTransfer.strLogPrefixShort}transfer loop from ` + + `${optsTransfer.chainNameSrc} to ${optsTransfer.chainNameDst}: `; + if( gIsOneTransferInProgressInThisThread ) { + optsTransfer.details.warning( "{p}Transfer loop step is skipped because previous one " + + "is still in progress", optsTransfer.strLogPrefix ); + if( log.exposeDetailsGet() && optsTransfer.details.exposeDetailsTo ) { + optsTransfer.details.exposeDetailsTo( + log.globalStream(), optsTransfer.strGatheredDetailsName, true ); + } + optsTransfer.details.close(); + return false; + } + try { + gIsOneTransferInProgressInThisThread = true; + optsTransfer.details.debug( "{p}Message signing is {oo}", + optsTransfer.strLogPrefix, optsTransfer.imaState.bSignMessages ); + if( optsTransfer.fnSignMessages == null || optsTransfer.fnSignMessages == undefined || + ( !optsTransfer.imaState.bSignMessages ) + ) { + optsTransfer.details.debug( "{p}Using internal signing stub function", + optsTransfer.strLogPrefix ); + optsTransfer.fnSignMessages = async function( + nTransferLoopCounter: number, jarrMessages: any[], + nIdxCurrentMsgBlockStart: number, strFromChainName: string, + joExtraSignOpts?: loop.TExtraSignOpts | null, + fnAfter?: TFunctionAfterSigningMessages + ) { + optsTransfer.details.debug( + "{p}Message signing callback was not provided to IMA, first real message " + + "index is: {}, have {} message(s) to process {}", optsTransfer.strLogPrefix, + nIdxCurrentMsgBlockStart, optsTransfer.jarrMessages.length, + optsTransfer.jarrMessages ); + if( fnAfter ) // null - no error, null - no signatures + await fnAfter( null, jarrMessages, null ); + }; + } else { + optsTransfer.details.debug( "{p}Using externally provided signing function", + optsTransfer.strLogPrefix ); + } + optsTransfer.nTransactionsCountInBlock = optsTransfer.nTransactionsCountInBlock || 5; + optsTransfer.nTransferSteps = optsTransfer.nTransferSteps || Number.MAX_SAFE_INTEGER; + optsTransfer.nMaxTransactionsCount = + optsTransfer.nMaxTransactionsCount || Number.MAX_SAFE_INTEGER; + if( optsTransfer.nTransactionsCountInBlock < 1 ) + optsTransfer.nTransactionsCountInBlock = 1; + if( optsTransfer.nBlockAwaitDepth < 0 ) + optsTransfer.nBlockAwaitDepth = 0; + if( optsTransfer.nBlockAge < 0 ) + optsTransfer.nBlockAge = 0; + try { + if( !( await doQueryOutgoingMessageCounter( optsTransfer ) ) ) { + gIsOneTransferInProgressInThisThread = false; + return false; + } + if( !( await doMainTransferLoopActions( optsTransfer ) ) ) { + gIsOneTransferInProgressInThisThread = false; + return false; + } + } catch ( err ) { + optsTransfer.details.critical( "{p}Error in {} during {bright}: {err}, " + + "stack is:\n{stack}", optsTransfer.strLogPrefix, + optsTransfer.strGatheredDetailsName, optsTransfer.strActionName, err, err ); + optsTransfer.details.exposeDetailsTo( log.globalStream(), + optsTransfer.strGatheredDetailsName, false ); + imaTransferErrorHandling.saveTransferError( + optsTransfer.strTransferErrorCategoryName, optsTransfer.details.toString() ); + optsTransfer.details.close(); + gIsOneTransferInProgressInThisThread = false; + return false; + } + imaGasUsage.printGasUsageReportFromArray( "TRANSFER " + optsTransfer.chainNameSrc + + " to " + optsTransfer.chainNameDst, optsTransfer.jarrReceipts, optsTransfer.details ); + if( optsTransfer.details ) { + if( log.exposeDetailsGet() && optsTransfer.details.exposeDetailsTo ) { + optsTransfer.details.exposeDetailsTo( + log.globalStream(), optsTransfer.strGatheredDetailsName, true ); + } + optsTransfer.details.close(); + } + if( !optsTransfer.bErrorInSigningMessages ) { + imaTransferErrorHandling.saveTransferSuccess( + optsTransfer.strTransferErrorCategoryName ); + } + gIsOneTransferInProgressInThisThread = false; + return true; + } catch ( err ) { + gIsOneTransferInProgressInThisThread = false; + optsTransfer.details.error( + "{p}Transfer loop step failed with error: {err} in {}, stack is:\n{stack}", + optsTransfer.strLogPrefix, err, threadInfo.threadDescription(), err ); + if( log.exposeDetailsGet() && optsTransfer.details.exposeDetailsTo ) { + optsTransfer.details.exposeDetailsTo( + log.globalStream(), optsTransfer.strGatheredDetailsName, true ); + } + optsTransfer.details.close(); + return false; + } +} + +export async function doAllS2S( // s-chain --> s-chain + joRuntimeOpts: loop.TRuntimeOpts, + imaState: state.TIMAState, + skaleObserver: any, + ethersProviderDst: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joMessageProxyDst: owaspUtils.ethersMod.ethers.Contract, + joAccountDst: state.TAccount, + chainNameDst: string, + chainIdDst: string, + joTokenManagerSChain: owaspUtils.ethersMod.ethers.Contract, // for logs validation on s-chain + nTransactionsCountInBlock: number, + nTransferSteps: number, + nMaxTransactionsCount: number, + nBlockAwaitDepth: number, + nBlockAge: number, + fnSignMessages: TFunctionDoSignMessages, + transactionCustomizerDst: imaTx.TransactionCustomizer +): Promise { + let cntOK = 0; let cntFail = 0; let nIndexS2S = 0; + const sc = imaState.chainProperties.sc; + const strDirection = "S2S"; + const arrSChainsCached = skaleObserver.getLastCachedSChains(); + const cntSChains = arrSChainsCached.length; + log.information( "Have {} S-Chain(s) connected to this S-Chain for performing " + + "S2S transfers in {}.", cntSChains, threadInfo.threadDescription() ); + for( let idxSChain = 0; idxSChain < cntSChains; ++idxSChain ) { + const joSChain = arrSChainsCached[idxSChain]; + const urlSrc = skaleObserver.pickRandomSChainUrl( joSChain ); + const ethersProviderSrc: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider = + owaspUtils.getEthersProviderFromURL( urlSrc ); + const joAccountSrc = joAccountDst; // ??? + const chainNameSrc = joSChain.name; + const chainIdSrc = joSChain.chainId; + log.information( "S2S transfer walk trough {}/{} S-Chain in {}...", + chainNameSrc, chainIdSrc, threadInfo.threadDescription() ); + let bOK = false; + try { + nIndexS2S = idxSChain; + if( !await pwa.checkOnLoopStart( imaState, "s2s", nIndexS2S ) ) { + imaState.loopState.s2s.wasInProgress = false; + log.notice( "Skipped(s2s) due to cancel mode reported from PWA in {}", + threadInfo.threadDescription() ); + } else { + if( loop.checkTimeFraming( null, "s2s", joRuntimeOpts ) ) { + // ??? assuming all S-Chains have same ABIs here + const joMessageProxySrc: owaspUtils.ethersMod.ethers.Contract = + new owaspUtils.ethersMod.ethers.Contract( + sc.joAbiIMA.message_proxy_chain_address, + sc.joAbiIMA.message_proxy_chain_abi, + ethersProviderSrc ); + const joDepositBoxSrc: owaspUtils.ethersMod.ethers.Contract = + new owaspUtils.ethersMod.ethers.Contract( + sc.joAbiIMA.message_proxy_chain_address, + sc.joAbiIMA.message_proxy_chain_abi, + ethersProviderSrc ); + const joExtraSignOpts: loop.TExtraSignOpts = { + chainNameSrc, + chainIdSrc, + chainNameDst, + chainIdDst, + joAccountSrc, + joAccountDst, + ethersProviderSrc, + ethersProviderDst + }; + joRuntimeOpts.idxChainKnownForS2S = idxSChain; + joRuntimeOpts.cntChainsKnownForS2S = cntSChains; + joRuntimeOpts.joExtraSignOpts = joExtraSignOpts; + + imaState.loopState.s2s.isInProgress = true; + await pwa.notifyOnLoopStart( imaState, "s2s", nIndexS2S ); + + bOK = await doTransfer( + strDirection, + joRuntimeOpts, + ethersProviderSrc, + joMessageProxySrc, + joAccountSrc, + ethersProviderDst, + joMessageProxyDst, + joAccountDst, + chainNameSrc, + chainNameDst, + chainIdSrc, + chainIdDst, + joDepositBoxSrc, // for logs validation on mainnet or source S-Chain + joTokenManagerSChain, // for logs validation on s-chain + nTransactionsCountInBlock, + nTransferSteps, + nMaxTransactionsCount, + nBlockAwaitDepth, + nBlockAge, + fnSignMessages, + joExtraSignOpts, + transactionCustomizerDst ); + imaState.loopState.s2s.isInProgress = false; + await pwa.notifyOnLoopEnd( imaState, "s2s", nIndexS2S ); + } else { + bOK = true; + const strLogPrefix = "S2S Loop: "; + log.notice( "Skipped(s2s) in {} due to time framing check", + strLogPrefix, threadInfo.threadDescription() ); + } + } + } catch ( err ) { + bOK = false; + log.error( "S2S step error from S-Chain {}, error is: {err} in {}, stack is:\n{stack}", + chainNameSrc, err, threadInfo.threadDescription(), err ); + imaState.loopState.s2s.isInProgress = false; + await pwa.notifyOnLoopEnd( imaState, "s2s", nIndexS2S ); + } + if( bOK ) + ++cntOK; + else + ++cntFail; + } + joRuntimeOpts.idxChainKnownForS2S = 0; // reset/clear + joRuntimeOpts.cntChainsKnownForS2S = 0; // reset/clear + if( "joExtraSignOpts" in joRuntimeOpts ) + delete joRuntimeOpts.joExtraSignOpts; // reset/clear + if( cntOK > 0 || cntFail > 0 ) { + let s = log.fmtDebug( "Stats for S2S steps in {}: ", threadInfo.threadDescription() ); + if( cntOK > 0 ) { + s += " " + log.fmtInformation( "{p}", cntOK ) + " " + + log.fmtSuccess( "S-Chain(s) processed OKay" ) + log.fmtDebug( ", " ); + } + if( cntFail > 0 ) { + s += " " + log.fmtInformation( "{p}", cntFail ) + " " + + log.fmtError( "S-Chain(s) failed" ); + } + log.debug( s ); + } + return ( cntFail == 0 ); +} diff --git a/src/imaEthOperations.ts b/src/imaEthOperations.ts new file mode 100644 index 00000000..fa8dedc6 --- /dev/null +++ b/src/imaEthOperations.ts @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaEthOperations.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaTx from "./imaTx.js"; +import * as imaGasUsage from "./imaGasUsageOperations.js"; +import * as imaEventLogScan from "./imaEventLogScan.js"; +import * as threadInfo from "./threadInfo.js"; +import type * as state from "./state.js"; + +export async function getBalanceEth( + isMainNet: boolean, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider | null, + chainId: string, + joAccount: state.TAccount | null, + contractERC20: owaspUtils.ethersMod.Contract | null +): Promise { + const strLogPrefix = "getBalanceEth() call "; + try { + if( !( ethersProvider && joAccount ) ) + return ""; + const strAddress = joAccount.address(); + if( ( !isMainNet ) && contractERC20 ) { + const balance = + await contractERC20.callStatic.balanceOf( strAddress, { from: strAddress } ); + return balance; + } + const balance = await ethersProvider.getBalance( strAddress ); + return balance; + } catch ( err ) { + log.error( "{p}balance fetching error details: {err}, stack is:\n{stack}", + strLogPrefix, err, err ); + } + return ""; +} + +// transfer money from main-net to S-chain +// main-net.DepositBox call: function deposit(string schainName, address to) public payable +// Where: +// schainName...obvious +// to.........address in S-chain +// Notice: +// this function is available for everyone in main-net +// money is sent from caller +// "value" JSON arg is used to specify amount of money to sent +export async function doEthPaymentFromMainNet( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, + joAccountSrc: state.TAccount, + joAccountDst: state.TAccount, + joDepositBox: owaspUtils.ethersMod.ethers.Contract, + joMessageProxyMainNet: owaspUtils.ethersMod.ethers.Contract, // for checking logs + chainIdSChain: string, + weiHowMuch: any, // how much WEI money to send + transactionCustomizerMainNet: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any[] = []; + let strActionName = ""; + const strLogPrefix = "M2S ETH Payment: "; + try { + details.debug( "{p}Doing payment from mainnet with chainIdSChain={}...", + strLogPrefix, chainIdSChain ); + strActionName = "ETH payment from Main Net, deposit"; + const arrArguments = [ + chainIdSChain + ]; + const gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGas = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "DepositBox", joDepositBox, "deposit", arrArguments, + joAccountSrc, strActionName, + gasPrice, 3000000 ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGas ); + const isIgnore = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProviderMainNet, + "DepositBox", joDepositBox, "deposit", arrArguments, + joAccountSrc, strActionName, isIgnore, + gasPrice, estimatedGas ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const joReceipt = await imaTx.payedCall( + details, ethersProviderMainNet, + "DepositBox", joDepositBox, "deposit", arrArguments, + joAccountSrc, strActionName, + gasPrice, estimatedGas, weiHowMuch ); + if( joReceipt ) { + jarrReceipts.push( { + description: "doEthPaymentFromMainNet", + receipt: joReceipt + } ); + } + + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxyMainNet ) { + details.debug( "{p}Verifying the {} event of the ", "MessageProxy/{} contract ...", + strLogPrefix, strEventName, joMessageProxyMainNet.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderMainNet, joMessageProxyMainNet, strEventName, + joReceipt.blockNumber, joReceipt.transactionHash, + joMessageProxyMainNet.filters[strEventName]() + ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{} contract, " + + "found event(s): {}", strLogPrefix, strEventName, + joMessageProxyMainNet.address, joEvents ); + } else { + throw new Error( "Verification failed for the OutgoingMessage event of the " + + `MessageProxy ${joMessageProxyMainNet.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doEthPaymentFromMainNet", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( "ETH PAYMENT FROM MAIN NET", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doEthPaymentFromMainNet", true ); + details.close(); + return true; +} + +// transfer money from S-chain to main-net +// S-chain.TokenManager call: function exitToMain(address to) public payable +// Where: +// to.........address in main-net +// Notice: +// this function is available for everyone in S-chain +// money is sent from caller +// "value" JSON arg is used to specify amount of money to sent +export async function doEthPaymentFromSChain( + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdSChain: string, + joAccountSrc: state.TAccount, + joAccountDst: state.TAccount, + joTokenManagerETH: owaspUtils.ethersMod.ethers.Contract, + joMessageProxySChain: owaspUtils.ethersMod.ethers.Contract, // for checking logs + weiHowMuch: any, // how much WEI money to send + transactionCustomizerSChain: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "S2M ETH Payment: "; + try { + strActionName = "ETH payment from S-Chain, exitToMain"; + const arrArguments = [ + owaspUtils.toBN( weiHowMuch ) + ]; + const gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGas = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "TokenManagerETH", joTokenManagerETH, "exitToMain", arrArguments, + joAccountSrc, strActionName, + gasPrice, 6000000, 0, null ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGas ); + const isIgnore = true; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProviderSChain, + "TokenManagerETH", joTokenManagerETH, "exitToMain", arrArguments, + joAccountSrc, strActionName, isIgnore, + gasPrice, estimatedGas, 0, null ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const opts: imaTx.TCustomPayedCallOptions = { + isCheckTransactionToSchain: true + }; + const joReceipt = await imaTx.payedCall( + details, ethersProviderSChain, + "TokenManagerETH", joTokenManagerETH, "exitToMain", arrArguments, + joAccountSrc, strActionName, + gasPrice, estimatedGas, 0, opts ); + if( joReceipt ) { + jarrReceipts.push( { + description: "doEthPaymentFromSChain", + receipt: joReceipt + } ); + } + + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxySChain ) { + details.debug( "{p}Verifying the {} event of the MessageProxy/{} contract ...", + strLogPrefix, strEventName, joMessageProxySChain.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderSChain, joMessageProxySChain, strEventName, + joReceipt.blockNumber, joReceipt.transactionHash, + joMessageProxySChain.filters[strEventName]() + ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{} contract, " + + "found event(s): {}", strLogPrefix, strEventName, joMessageProxySChain.address, + joEvents ); + } else { + throw new Error( "Verification failed for the OutgoingMessage event of the " + + `MessageProxy ${joMessageProxySChain.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doEthPaymentFromSChain", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( "ETH PAYMENT FROM S-CHAIN", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doEthPaymentFromSChain", true ); + details.close(); + return true; +} + +export async function receiveEthPaymentFromSchainOnMainNet( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, + joAccountMN: state.TAccount, + joDepositBoxETH: owaspUtils.ethersMod.ethers.Contract, + transactionCustomizerMainNet: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "M2S ETH Receive: "; + try { + strActionName = "Receive ETH payment from S-Chain on Main Met, getMyEth"; + const arrArguments: any = []; + const gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGas = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "DepositBoxETH", joDepositBoxETH, "getMyEth", arrArguments, + joAccountMN, strActionName, + gasPrice, 3000000 ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGas ); + const isIgnore = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProviderMainNet, + "DepositBoxETH", joDepositBoxETH, + "getMyEth", arrArguments, + joAccountMN, strActionName, isIgnore, + gasPrice, estimatedGas ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const joReceipt = await imaTx.payedCall( + details, ethersProviderMainNet, + "DepositBoxETH", joDepositBoxETH, + "getMyEth", arrArguments, + joAccountMN, strActionName, + gasPrice, estimatedGas ); + if( joReceipt ) { + jarrReceipts.push( { + description: "receiveEthPaymentFromSchainOnMainNet", + receipt: joReceipt + } ); + } + } catch ( err ) { + details.critical( "{p}Receive payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), + "receiveEthPaymentFromSchainOnMainNet", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( "RECEIVE ETH ON MAIN NET", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "receiveEthPaymentFromSchainOnMainNet", true ); + details.close(); + return true; +} + +export async function viewEthPaymentFromSchainOnMainNet( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joAccountMN: state.TAccount, + joDepositBoxETH: owaspUtils.ethersMod.ethers.Contract +): Promise { + const details = log.createMemoryStream(); + const strActionName = ""; + const strLogPrefix = "S ETH View: "; + try { + if( !( ethersProviderMainNet && joAccountMN && joDepositBoxETH ) ) + return null; + const addressFrom = joAccountMN.address(); + const xWei = + await joDepositBoxETH.callStatic.approveTransfers( + addressFrom, + { from: addressFrom } ); + details.success( "{p}You can receive(wei): {}", strLogPrefix, xWei ); + const xEth = owaspUtils.ethersMod.ethers.utils.formatEther( owaspUtils.toBN( xWei ) ); + details.success( "{p}You can receive(eth): {}", strLogPrefix, xEth ); + if( log.exposeDetailsGet() ) { + details.exposeDetailsTo( log.globalStream(), + "viewEthPaymentFromSchainOnMainNet", true ); + } + details.close(); + return xWei; + } catch ( err ) { + details.critical( "{p}View payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "viewEthPaymentFromSchainOnMainNet", false ); + details.close(); + return null; + } +} diff --git a/src/imaEventLogScan.ts b/src/imaEventLogScan.ts new file mode 100644 index 00000000..4bce7eab --- /dev/null +++ b/src/imaEventLogScan.ts @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaEventLogScan.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as rpcCall from "./rpcCall.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaTransferErrorHandling from "./imaTransferErrorHandling.js"; + +export function createProgressiveEventsScanPlan( + details: log.TLogger, nLatestBlockNumber: any +): any[] { + // assume Main Net mines 6 blocks per minute + const blocksInOneMinute = 6; + const blocksInOneHour = blocksInOneMinute * 60; + const blocksInOneDay = blocksInOneHour * 24; + const blocksInOneWeek = blocksInOneDay * 7; + const blocksInOneMonth = blocksInOneDay * 31; + const blocksInOneYear = blocksInOneDay * 366; + const blocksInThreeYears = blocksInOneYear * 3; + const arrProgressiveEventsScanPlanA = [ { + nBlockFrom: + nLatestBlockNumber - blocksInOneDay, + nBlockTo: "latest", + type: "1 day" + }, { + nBlockFrom: + nLatestBlockNumber - blocksInOneWeek, + nBlockTo: "latest", + type: "1 week" + }, { + nBlockFrom: + nLatestBlockNumber - blocksInOneMonth, + nBlockTo: "latest", + type: "1 month" + }, { + nBlockFrom: + nLatestBlockNumber - blocksInOneYear, + nBlockTo: "latest", + type: "1 year" + }, { + nBlockFrom: + nLatestBlockNumber - blocksInThreeYears, + nBlockTo: "latest", + type: "3 years" + } ]; + const arrProgressiveEventsScanPlan: any[] = []; + for( let idxPlan = 0; idxPlan < arrProgressiveEventsScanPlanA.length; ++idxPlan ) { + const joPlan = arrProgressiveEventsScanPlanA[idxPlan]; + if( joPlan.nBlockFrom >= 0 ) + arrProgressiveEventsScanPlan.push( joPlan ); + } + if( arrProgressiveEventsScanPlan.length > 0 ) { + const joLastPlan = + arrProgressiveEventsScanPlan[arrProgressiveEventsScanPlan.length - 1]; + if( !( joLastPlan.nBlockFrom == 0 && joLastPlan.nBlockTo == "latest" ) ) { + arrProgressiveEventsScanPlan.push( + { nBlockFrom: 0, nBlockTo: "latest", type: "entire block range" } ); + } + } else { + arrProgressiveEventsScanPlan.push( + { nBlockFrom: 0, nBlockTo: "latest", type: "entire block range" } ); + } + return arrProgressiveEventsScanPlan; +} + +export async function safeGetPastEventsProgressive( + details: log.TLogger, strLogPrefix: string, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + attempts: number, joContract: owaspUtils.ethersMod.ethers.Contract, strEventName: string, + nBlockFrom: any, nBlockTo: any, joFilter: any +): Promise { + const strURL = owaspUtils.ethersProviderToUrl( ethersProvider ); + details.information( "{p}Will run progressive logs search for event {} via URL {url}, " + + "from block {}, to block...", strLogPrefix, strEventName, strURL, nBlockFrom, nBlockTo ); + if( !imaTransferErrorHandling.getEnabledProgressiveEventsScan() ) { + details.warning( + "{p}IMPORTANT NOTICE: Will skip progressive events scan in block range from {} to {} " + + "because it's {}", strLogPrefix, nBlockFrom, nBlockTo, log.fmtError( "DISABLED" ) ); + return await safeGetPastEvents( details, strLogPrefix, ethersProvider, attempts, + joContract, strEventName, nBlockFrom, nBlockTo, joFilter ); + } + const nLatestBlockNumber = owaspUtils.toBN( + await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) ); + const nLatestBlockNumberPlus1 = nLatestBlockNumber.add( owaspUtils.toBN( 1 ) ); + let isLastLatest = false; + if( nBlockTo == "latest" ) { + isLastLatest = true; + nBlockTo = nLatestBlockNumberPlus1; + details.trace( "{p}Progressive event log records scan up to latest block #{} " + + "assumed instead of {}", strLogPrefix, nBlockTo.toHexString(), "latest" ); + } else { + nBlockTo = owaspUtils.toBN( nBlockTo ); + if( nBlockTo.gte( nLatestBlockNumber ) ) + isLastLatest = true; + } + nBlockFrom = owaspUtils.toBN( nBlockFrom ); + const nBlockZero = owaspUtils.toBN( 0 ); + const isFirstZero = !!( nBlockFrom.eq( nBlockZero ) ); + if( !( isFirstZero && isLastLatest ) ) { + details.trace( "{p}Will skip progressive event log records scan and use scan in block " + + "range from {} to {}", strLogPrefix, nBlockFrom.toHexString(), nBlockTo.toHexString() ); + return await safeGetPastEvents( + details, strLogPrefix, + ethersProvider, attempts, joContract, strEventName, + nBlockFrom, nBlockTo, joFilter + ); + } + details.trace( "{p}Current latest block number is {}", + strLogPrefix, nLatestBlockNumber.toHexString() ); + const arrProgressiveEventsScanPlan = + createProgressiveEventsScanPlan( details, nLatestBlockNumberPlus1 ); + details.trace( "Composed progressive event log records scan plan is: {}", + arrProgressiveEventsScanPlan ); + let joLastPlan: any = { nBlockFrom: 0, nBlockTo: "latest", type: "entire block range" }; + for( let idxPlan = 0; idxPlan < arrProgressiveEventsScanPlan.length; ++idxPlan ) { + const joPlan = arrProgressiveEventsScanPlan[idxPlan]; + if( joPlan.nBlockFrom < 0 ) + continue; + joLastPlan = joPlan; + details.trace( + "{p}Progressive event log records scan of {} event, from block {}, to block {}, " + + "plan type is {} via URL {url}...", + strLogPrefix, strEventName, joPlan.nBlockFrom, joPlan.nBlockTo, joPlan.type, strURL ); + try { + const joAllEventsInBlock = await safeGetPastEventsIterative( details, strLogPrefix, + ethersProvider, attempts, joContract, strEventName, + joPlan.nBlockFrom, joPlan.nBlockTo, joFilter ); + if( joAllEventsInBlock && joAllEventsInBlock.length > 0 ) { + details.success( + "{p}Progressive event log records scan of log event {}, from block {}, " + + "to block {}, block range is {}, via URL {url}, found {} event(s)", + strLogPrefix, strEventName, joPlan.nBlockFrom, joPlan.nBlockTo, joPlan.type, + strURL, joAllEventsInBlock.length ); + return joAllEventsInBlock; + } + } catch ( err ) {} + } + details.error( + "{p}Was not found(progressive) event log record for event {}, from block {}" + + ", to block {}, block range is {}, via URL {url}, using progressive event log records scan", + strLogPrefix, strEventName, joLastPlan.nBlockFrom, joLastPlan.nBlockTo, joLastPlan.type, + strURL ); + return []; +} + +export async function getContractCallEvents( + details: log.TLogger, strLogPrefix: string, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joContract: owaspUtils.ethersMod.ethers.Contract, strEventName: string, + nBlockNumber: any, strTxHash: string, joFilter: any +): Promise { + joFilter = joFilter || {}; + nBlockNumber = owaspUtils.toBN( nBlockNumber ); + const n10 = owaspUtils.toBN( 10 ); + let nBlockFrom = nBlockNumber.sub( n10 ); let nBlockTo = nBlockNumber.add( n10 ); + const nBlockZero = owaspUtils.toBN( 0 ); + const nLatestBlockNumber = owaspUtils.toBN( + await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) ); + const nLatestBlockNumberPlus1 = nLatestBlockNumber.add( owaspUtils.toBN( 1 ) ); + if( nBlockFrom.lt( nBlockZero ) ) + nBlockFrom = nBlockZero; + if( nBlockTo.gte( nLatestBlockNumber ) ) + nBlockTo = nLatestBlockNumberPlus1; + const joAllEventsInBlock = await safeGetPastEventsIterative( + details, strLogPrefix, ethersProvider, 10, joContract, strEventName, + nBlockFrom, nBlockTo, joFilter ); + const joAllTransactionEvents: any = []; + let i: number; + for( i = 0; i < joAllEventsInBlock.length; ++i ) { + const joEvent = joAllEventsInBlock[i]; + if( "transactionHash" in joEvent && joEvent.transactionHash == strTxHash ) + joAllTransactionEvents.push( joEvent ); + } + return joAllTransactionEvents; +} + +export async function safeGetTransactionCount( + details: log.TLogger, cntAttempts: number, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + address: string, param: any, + retValOnFail: any, throwIfServerOffline: boolean +): Promise { + const strFnName = "getTransactionCount"; + const u = owaspUtils.ethersProviderToUrl( ethersProvider ); + const nWaitStepMilliseconds = 10 * 1000; + if( throwIfServerOffline == null || throwIfServerOffline == undefined ) + throwIfServerOffline = true; + cntAttempts = ( owaspUtils.parseIntOrHex( cntAttempts ) < 1 ) + ? 1 + : owaspUtils.parseIntOrHex( cntAttempts ); + if( retValOnFail == null || retValOnFail == undefined ) + retValOnFail = ""; + let ret = retValOnFail; + let idxAttempt = 1; + for( ; idxAttempt <= cntAttempts; ++idxAttempt ) { + const isOnLine = await rpcCall.checkUrl( u, nWaitStepMilliseconds ); + if( !isOnLine ) { + ret = retValOnFail; + if( !throwIfServerOffline ) + return ret; + details.error( "Cannot call {} via {url} because server is off-line, attempt {} of {}", + strFnName + "()", u, idxAttempt, cntAttempts ); + throw new Error( `Cannot ${strFnName}() via ${u} because server is off-line` ); + } + details.trace( "Call to {} via {url}, attempt {} of {}", + strFnName + "()", u, idxAttempt, cntAttempts ); + try { + ret = await ethersProvider[strFnName]( address, param ); + return ret; + } catch ( err ) { + ret = retValOnFail; + details.error( "Failed call attempt {} of {} to {} via {url}, error is: {err}, " + + "stack is:\n{stack}", idxAttempt, cntAttempts, strFnName + "()", u, err, err ); + } + } + if( ( idxAttempt + 1 ) > cntAttempts && ret === "" ) { + details.error( "Failed call to {} via {url} after {} attempts", + strFnName + "()", u, cntAttempts ); + throw new Error( `Failed call to ${strFnName}() via ${u} after ${cntAttempts} attempts` ); + } + return ret; +} + +export async function safeGetTransactionReceipt( + details: log.TLogger, cntAttempts: number, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + txHash: string, retValOnFail?: any, throwIfServerOffline?: boolean +): Promise { + const strFnName = "getTransactionReceipt"; + const u = owaspUtils.ethersProviderToUrl( ethersProvider ); + const nWaitStepMilliseconds = 10 * 1000; + if( throwIfServerOffline == null || throwIfServerOffline == undefined ) + throwIfServerOffline = true; + cntAttempts = ( owaspUtils.parseIntOrHex( cntAttempts ) < 1 ) + ? 1 + : owaspUtils.parseIntOrHex( cntAttempts ); + if( retValOnFail == null || retValOnFail == undefined ) + retValOnFail = ""; + let ret = retValOnFail; + let idxAttempt = 1; + for( ; idxAttempt <= cntAttempts; ++idxAttempt ) { + const isOnLine = await rpcCall.checkUrl( u, nWaitStepMilliseconds ); + if( !isOnLine ) { + ret = retValOnFail; + if( !throwIfServerOffline ) + return ret; + details.error( "Cannot call {} via {url} because server is off-line, attempt {} of {}", + strFnName + "()", u, idxAttempt, cntAttempts ); + throw new Error( `Cannot ${strFnName}() via ${u} because server is off-line` ); + } + details.trace( "Call to {} via {url}, attempt {} of {}", + strFnName + "()", u, idxAttempt, cntAttempts ); + try { + ret = await ethersProvider[strFnName]( txHash ); + return ret; + } catch ( err ) { + ret = retValOnFail; + details.error( "Failed call attempt {} of {} to {} via {url}, error is: {err}, " + + "stack is:\n{stack}", idxAttempt, cntAttempts, strFnName + "()", u, err, err ); + } + } + if( ( idxAttempt + 1 ) > cntAttempts ) { + details.error( "Failed call to {} via {url} after {} attempts", + strFnName + "()", u, cntAttempts ); + throw new Error( `Failed call to ${strFnName}() via ${u} after ${cntAttempts} attempts` ); + } + return ret; +} + +export async function safeGetPastEvents( + details: log.TLogger, strLogPrefix: string, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + cntAttempts: number, joContract: owaspUtils.ethersMod.ethers.Contract, strEventName: string, + nBlockFrom: any, nBlockTo: any, joFilter: any, + retValOnFail?: any, throwIfServerOffline?: boolean +): Promise { + const u = owaspUtils.ethersProviderToUrl( ethersProvider ); + const nWaitStepMilliseconds = 10 * 1000; + if( throwIfServerOffline == null || throwIfServerOffline == undefined ) + throwIfServerOffline = true; + cntAttempts = ( owaspUtils.parseIntOrHex( cntAttempts ) < 1 ) + ? 1 + : owaspUtils.parseIntOrHex( cntAttempts ); + if( retValOnFail == null || retValOnFail == undefined ) + retValOnFail = ""; + let ret = retValOnFail; + const nLatestBlockNumber = owaspUtils.toBN( + await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) ); + let idxAttempt = 1; + const strErrorTextAboutNotExistingEvent = + "Event \"" + strEventName + "\" doesn't exist in this contract"; + if( nBlockTo == "latest" ) { + const nLatestBlockNumberPlus1 = nLatestBlockNumber.add( owaspUtils.toBN( 1 ) ); + nBlockTo = nLatestBlockNumberPlus1; + } else + nBlockTo = owaspUtils.toBN( nBlockTo ); + nBlockFrom = owaspUtils.toBN( nBlockFrom ); + for( ; idxAttempt <= cntAttempts; ++idxAttempt ) { + const isOnLine = await rpcCall.checkUrl( u, nWaitStepMilliseconds ); + if( !isOnLine ) { + ret = retValOnFail; + if( !throwIfServerOffline ) + return ret; + details.error( + "{p}Cannot do {} event filtering via {url} because server is off-line, " + + "attempt {} of {}", strLogPrefix, strEventName, u, idxAttempt, cntAttempts ); + throw new Error( `Cannot do ${strEventName} event filtering, ` + + `from block ${nBlockFrom.toHexString()} , to block ${nBlockTo.toHexString()} ` + + `via ${u} because server is off-line` ); + } + details.trace( "{p}Repeat {} event filtering via {url}, attempt {} of {}", + strLogPrefix, strEventName, u, idxAttempt, cntAttempts ); + try { + details.trace( + "{p}Attempt {} of, will query filter {} on contract {} from block {} to block {}", + strLogPrefix, idxAttempt, cntAttempts, joFilter, joContract.address, + nBlockFrom.toHexString(), nBlockTo.toHexString() ); + ret = await joContract.queryFilter( joFilter, + nBlockFrom.toHexString(), nBlockTo.toHexString() ); + return ret; + } catch ( err ) { + ret = retValOnFail; + details.error( + "{p}Failed filtering attempt {} of {} for event {} via {url}, from block {}" + + ", to block{}, error is: {err}, stack is:\n{stack}", strLogPrefix, + idxAttempt, cntAttempts, strEventName, u, + nBlockFrom.toHexString(), nBlockTo.toHexString(), err, err ); + if( owaspUtils.extractErrorMessage( err ) + .includes( strErrorTextAboutNotExistingEvent ) + ) { + details.error( "{p}Did stopped {} event filtering because no such event exist " + + "in smart contract", strLogPrefix, strEventName ); + return ret; + } + } + } + if( ( idxAttempt + 1 ) === cntAttempts && ret === "" ) { + details.error( + "{p}Failed filtering attempt for {} event via {url}, from block {}, to block {} " + + "after {} attempts", strLogPrefix, strEventName, u, nBlockFrom.toHexString(), + nBlockTo.toHexString(), cntAttempts ); + throw new Error( `Failed filtering attempt for ${strEventName} event, ` + + `from block ${nBlockFrom.toHexString()}, to block ${nBlockTo.toHexString()} ` + + `via ${u} after ${cntAttempts} attempts` ); + } + return ret; +} + +export async function safeGetPastEventsIterative( + details: log.TLogger, strLogPrefix: string, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + attempts: number, joContract: owaspUtils.ethersMod.ethers.Contract, strEventName: string, + nBlockFrom: any, nBlockTo: any, joFilter: any +): Promise { + if( imaHelperAPIs.getBlocksCountInInIterativeStepOfEventsScan() <= 0 || + imaHelperAPIs.getMaxIterationsInAllRangeEventsScan() <= 0 ) { + details.warning( + "{p}IMPORTANT NOTICE: Will skip iterative events scan in block range from {} to {} " + + "because it's {}", strLogPrefix, nBlockFrom, nBlockTo, log.fmtError( "DISABLED" ) ); + return await safeGetPastEvents( details, strLogPrefix, ethersProvider, attempts, + joContract, strEventName, nBlockFrom, nBlockTo, joFilter ); + } + const nLatestBlockNumber = owaspUtils.toBN( + await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) ); + const nLatestBlockNumberPlus1 = nLatestBlockNumber.add( owaspUtils.toBN( 1 ) ); + let isLastLatest = false; + if( nBlockTo == "latest" ) { + isLastLatest = true; + nBlockTo = nLatestBlockNumberPlus1; + details.trace( "{p}Iterative scan up to latest block #{} assumed instead of {}", + strLogPrefix, nBlockTo.toHexString(), "latest" ); + } else { + nBlockTo = owaspUtils.toBN( nBlockTo ); + if( nBlockTo.gte( nLatestBlockNumber ) ) + isLastLatest = true; + } + nBlockFrom = owaspUtils.toBN( nBlockFrom ); + const nBlockZero = owaspUtils.toBN( 0 ); + const isFirstZero = !!( nBlockFrom.eq( nBlockZero ) ); + if( isFirstZero && isLastLatest ) { + if( nLatestBlockNumber.div( + owaspUtils.toBN( imaHelperAPIs.getBlocksCountInInIterativeStepOfEventsScan() ) + ).gt( owaspUtils.toBN( imaHelperAPIs.getMaxIterationsInAllRangeEventsScan() ) ) + ) { + details.warning( + "{p}IMPORTANT NOTICE: Will skip iterative scan and use scan in block range " + + "from {} to {}", strLogPrefix, nBlockFrom.toHexString(), nBlockTo.toHexString() ); + return await safeGetPastEvents( details, strLogPrefix, ethersProvider, attempts, + joContract, strEventName, nBlockFrom, nBlockTo, joFilter ); + } + } + details.trace( "{p}Iterative scan in {}/{} block range...", + strLogPrefix, nBlockFrom.toHexString(), nBlockTo.toHexString() ); + let idxBlockSubRangeTo = nBlockTo; + for( ; true; ) { + let idxBlockSubRangeFrom = idxBlockSubRangeTo.sub( + owaspUtils.toBN( imaHelperAPIs.getBlocksCountInInIterativeStepOfEventsScan() ) ); + if( idxBlockSubRangeFrom.lt( nBlockFrom ) ) + idxBlockSubRangeFrom = nBlockFrom; + try { + details.trace( "{p}Iterative scan of {}/{} block sub-range in {}/{} block range...", + strLogPrefix, idxBlockSubRangeFrom.toHexString(), idxBlockSubRangeTo.toHexString(), + nBlockFrom.toHexString(), nBlockTo.toHexString() ); + const joAllEventsInBlock = await safeGetPastEvents( details, strLogPrefix, + ethersProvider, attempts, joContract, strEventName, + idxBlockSubRangeFrom, idxBlockSubRangeTo, joFilter ); + if( joAllEventsInBlock && joAllEventsInBlock != "" && joAllEventsInBlock.length > 0 ) { + details.success( "{p}Result of iterative scan in {}/{} block range is {}", + strLogPrefix, nBlockFrom.toHexString(), nBlockTo.toHexString(), + joAllEventsInBlock ); + return joAllEventsInBlock; + } + } catch ( err ) { + details.critical( + "{p}Got scan error during interactive scan of {}/{} block sub-range in {}/{} " + + "block range, error is: {err}, stack is:\n{stack}", strLogPrefix, + idxBlockSubRangeFrom.toHexString(), idxBlockSubRangeTo.toHexString(), + nBlockFrom.toHexString(), nBlockTo.toHexString(), err, err ); + } + idxBlockSubRangeTo = idxBlockSubRangeFrom; + if( idxBlockSubRangeTo.lte( nBlockFrom ) ) + break; + } + details.debug( "{p}Result of iterative scan in {}/{} is {}", strLogPrefix, + nBlockFrom.toHexString(), nBlockTo.toHexString(), "empty block range" ); + return ""; +} diff --git a/src/imaGasUsageOperations.ts b/src/imaGasUsageOperations.ts new file mode 100644 index 00000000..10e49ccd --- /dev/null +++ b/src/imaGasUsageOperations.ts @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaGasUsageOperations.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; + +export function composeGasUsageReportFromArray( strName: string, jarrReceipts: any[] ): any { + if( !( strName && typeof strName === "string" && jarrReceipts ) ) + return { sumGasUsed: 0, strReport: "N/A" }; + let i; let sumGasUsed = owaspUtils.toBN( "0" ); + let s = "\n" + log.fmtInformation( "Gas usage report for " ) + + log.fmtInformation( "{p}\n", strName ); + for( i = 0; i < jarrReceipts.length; ++i ) { + try { + sumGasUsed = sumGasUsed.add( owaspUtils.toBN( jarrReceipts[i].receipt.gasUsed ) ); + s += log.fmtInformation( " {p}", jarrReceipts[i].description ) + + log.fmtDebug( "....." ) + + log.fmtInformation( "{p}\n", jarrReceipts[i].receipt.gasUsed.toString() ); + } catch ( err ) { } + } + s += " " + log.fmtAttention( "SUM" ) + log.fmtDebug( "....." ) + + log.fmtInformation( "{}", sumGasUsed.toString() ); + return { sumGasUsed, strReport: s }; +} + +export function printGasUsageReportFromArray( + strName: string, jarrReceipts: any[], details?: any ): void { + details = details || log; + const jo: any = composeGasUsageReportFromArray( strName, jarrReceipts ); + if( jo.strReport && typeof jo.strReport === "string" && jo.strReport.length > 0 && + jo.sumGasUsed?.gt( owaspUtils.toBN( "0" ) ) ) + log.information( jo.strReport ); +} diff --git a/src/imaHelperAPIs.ts b/src/imaHelperAPIs.ts new file mode 100644 index 00000000..23cc51f2 --- /dev/null +++ b/src/imaHelperAPIs.ts @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaHelperAPIs.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as rpcCall from "./rpcCall.js"; +import * as threadInfo from "./threadInfo.js"; + +export const longSeparator: string = + "============================================================" + + "==========================================================="; + +let gMillisecondsSleepBeforeFetchOutgoingMessageEvent: number = 5000; +let gMillisecondsSleepBetweenTransactionsOnSChain: number = 0; // example - 5000 +let gFlagWaitForNextBlockOnSChain: boolean = false; + +export function getMillisecondsSleepBeforeFetchOutgoingMessageEvent(): number { + return gMillisecondsSleepBeforeFetchOutgoingMessageEvent; +} +export function setMillisecondsSleepBeforeFetchOutgoingMessageEvent( val?: number ): void { + gMillisecondsSleepBeforeFetchOutgoingMessageEvent = val ?? 0; +} + +export function getSleepBetweenTransactionsOnSChainMilliseconds(): number { + return gMillisecondsSleepBetweenTransactionsOnSChain; +} +export function setSleepBetweenTransactionsOnSChainMilliseconds( val?: number ): void { + gMillisecondsSleepBetweenTransactionsOnSChain = val ?? 0; +} + +export function getWaitForNextBlockOnSChain(): boolean { + return ( !!gFlagWaitForNextBlockOnSChain ); +} +export function setWaitForNextBlockOnSChain( val: any ): void { + gFlagWaitForNextBlockOnSChain = ( !!val ); +} + +export const currentTimestamp = (): number => { + return Date.now().valueOf() / 1000; +}; + +export async function safeWaitForNextBlockToAppear( + details: log.TLogger, ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider +): Promise { + const nBlockNumber: any = + owaspUtils.toBN( await safeGetBlockNumber( details, 10, ethersProvider ) ); + details.trace( "Waiting for next block to appear..." ); + details.trace( " ...have block {}", nBlockNumber.toHexString() ); + for( ; true; ) { + await threadInfo.sleep( 1000 ); + const nBlockNumber2 = + owaspUtils.toBN( await safeGetBlockNumber( details, 10, ethersProvider ) ); + details.trace( " ...have block {}", nBlockNumber2.toHexString() ); + if( nBlockNumber2.gt( nBlockNumber ) ) + break; + } +} + +export async function safeGetBlockNumber( + details: log.TLogger, cntAttempts: number, + ethersProvider: owaspUtils.ethersMod.providers.JsonRpcProvider, + retValOnFail?: any, throwIfServerOffline?: boolean +): Promise { + const strFnName: string = "getBlockNumber"; + const u: string = owaspUtils.ethersProviderToUrl( ethersProvider ); + const nWaitStepMilliseconds = 10 * 1000; + if( throwIfServerOffline == null || throwIfServerOffline == undefined ) + throwIfServerOffline = true; + cntAttempts = ( owaspUtils.parseIntOrHex( cntAttempts ) < 1 ) + ? 1 + : owaspUtils.parseIntOrHex( cntAttempts ); + if( retValOnFail == null || retValOnFail == undefined ) + retValOnFail = ""; + let ret = retValOnFail; + let idxAttempt = 1; + for( ; idxAttempt <= cntAttempts; ++idxAttempt ) { + const isOnLine = await rpcCall.checkUrl( u, nWaitStepMilliseconds ); + if( !isOnLine ) { + ret = retValOnFail; + if( !throwIfServerOffline ) + return ret; + details.error( "Cannot call {} via {url} because server is off-line", + strFnName + "()", u ); + throw new Error( `Cannot ${strFnName}() via ${u} because server is off-line` ); + } + details.trace( "Repeat call to {} via {url}, attempt {}", strFnName + "()", u, idxAttempt ); + try { + ret = await ( ethersProvider as any )[strFnName](); + return ret; + } catch ( err ) { + ret = retValOnFail; + details.error( "Failed call attempt {} to via {url}, error is: {err}, " + + "stack is:\n{stack}", idxAttempt, strFnName + "()", u, err, err ); + } + } + if( ( idxAttempt + 1 ) > cntAttempts && ret === "" ) { + details.error( "Failed call to {} via {url} after {} attempts ", + strFnName + "()", u, cntAttempts ); + throw new Error( `Failed call to ${strFnName}() via ${u} after ${cntAttempts} attempts` ); + } + return ret; +} + +let gCountOfBlocksInIterativeStep: number = 1000; +let gMaxBlockScanIterationsInAllRange: number = 5000; + +export function getBlocksCountInInIterativeStepOfEventsScan(): number { + return gCountOfBlocksInIterativeStep; +} +export function setBlocksCountInInIterativeStepOfEventsScan( n?: number ): void { + if( !n ) + gCountOfBlocksInIterativeStep = 0; + else { + gCountOfBlocksInIterativeStep = owaspUtils.parseIntOrHex( n ); + if( gCountOfBlocksInIterativeStep < 0 ) + gCountOfBlocksInIterativeStep = 0; + } +} + +export function getMaxIterationsInAllRangeEventsScan(): number { + return gCountOfBlocksInIterativeStep; +} +export function setMaxIterationsInAllRangeEventsScan( n?: number ): void { + if( !n ) + gMaxBlockScanIterationsInAllRange = 0; + else { + gMaxBlockScanIterationsInAllRange = owaspUtils.parseIntOrHex( n ); + if( gMaxBlockScanIterationsInAllRange < 0 ) + gMaxBlockScanIterationsInAllRange = 0; + } +} + +// default S<->S transfer mode for "--s2s-transfer" is "forward" +let gFlagIsForwardS2S: boolean = true; + +export function getS2STransferModeDescription(): string { + return gFlagIsForwardS2S ? "forward" : "reverse"; +} + +export function getS2STransferModeDescriptionColorized(): string { + return log.posNeg( gFlagIsForwardS2S, "forward", "reverse" ); +} + +export function isForwardS2S(): boolean { + return ( !!gFlagIsForwardS2S ); +} + +export function isReverseS2S(): boolean { + return ( !!gFlagIsForwardS2S ); +} + +export function setForwardS2S( b?: boolean ): void { + if( b == null || b == undefined ) + b = true; + gFlagIsForwardS2S = ( !!b ); +} + +export function setReverseS2S( b?: boolean ): void { + if( b == null || b == undefined ) + b = true; + gFlagIsForwardS2S = !b; +} diff --git a/src/imaOracleOperations.ts b/src/imaOracleOperations.ts new file mode 100644 index 00000000..10d1b153 --- /dev/null +++ b/src/imaOracleOperations.ts @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaOracleOperations.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as imaOracle from "./oracle.js"; +import * as imaTx from "./imaTx.js"; +import * as imaGasUsage from "./imaGasUsageOperations.js"; +import * as imaTransferErrorHandling from "./imaTransferErrorHandling.js"; +import type * as state from "./state.js"; +import type * as IMA from "./imaCore.js"; + +export type TFunctionSignMsgOracle = + ( u256: any, details: log.TLogger, fnAfter: IMA.TFunctionAfterSigningMessages + ) => Promise ; + +export interface TGasPriceSetupOptions { + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider + transactionCustomizerSChain: imaTx.TransactionCustomizer + joCommunityLocker: owaspUtils.ethersMod.Contract + joAccountSC: state.TAccount + chainIdMainNet: string + chainIdSChain: string + fnSignMsgOracle: TFunctionSignMsgOracle + details: log.TLogger + jarrReceipts: any[] + strLogPrefix: string + strActionName: string + latestBlockNumber: any | null + latestBlock: any | null + bnTimestampOfBlock: any | null + bnTimeZoneOffset: any | null + gasPriceOnMainNet: any | null +} + +let gFlagIsEnabledOracle: boolean = false; + +export function getEnabledOracle(): boolean { + return ( !!gFlagIsEnabledOracle ); +} +export function setEnabledOracle( isEnabled: boolean ): void { + gFlagIsEnabledOracle = ( !!isEnabled ); +} + +async function prepareOracleGasPriceSetup( + optsGasPriceSetup: TGasPriceSetupOptions +): Promise { + optsGasPriceSetup.strActionName = + "prepareOracleGasPriceSetup.optsGasPriceSetup.latestBlockNumber()"; + optsGasPriceSetup.latestBlockNumber = + await optsGasPriceSetup.ethersProviderMainNet.getBlockNumber(); + optsGasPriceSetup.details.trace( "Latest block on Main Net is {}", + optsGasPriceSetup.latestBlockNumber ); + optsGasPriceSetup.strActionName = + "prepareOracleGasPriceSetup.optsGasPriceSetup.bnTimestampOfBlock()"; + optsGasPriceSetup.latestBlock = + await optsGasPriceSetup.ethersProviderMainNet + .getBlock( optsGasPriceSetup.latestBlockNumber ); + optsGasPriceSetup.bnTimestampOfBlock = + owaspUtils.toBN( optsGasPriceSetup.latestBlock.timestamp ); + optsGasPriceSetup.details.trace( "Local timestamp on Main Net is {}={} (original)", + optsGasPriceSetup.bnTimestampOfBlock.toString(), + owaspUtils.ensureStartsWith0x( optsGasPriceSetup.bnTimestampOfBlock.toHexString() ) ); + optsGasPriceSetup.bnTimeZoneOffset = owaspUtils.toBN( new Date( parseInt( + optsGasPriceSetup.bnTimestampOfBlock.toString(), 10 ) ).getTimezoneOffset() ); + optsGasPriceSetup.details.trace( "Local time zone offset is {}={} (original)", + optsGasPriceSetup.bnTimeZoneOffset.toString(), + owaspUtils.ensureStartsWith0x( optsGasPriceSetup.bnTimeZoneOffset.toHexString() ) ); + optsGasPriceSetup.bnTimestampOfBlock = + optsGasPriceSetup.bnTimestampOfBlock.add( optsGasPriceSetup.bnTimeZoneOffset ); + optsGasPriceSetup.details.trace( "UTC timestamp on Main Net is {}={} (original)", + optsGasPriceSetup.bnTimestampOfBlock.toString(), + owaspUtils.ensureStartsWith0x( optsGasPriceSetup.bnTimestampOfBlock.toHexString() ) ); + const bnValueToSubtractFromTimestamp = owaspUtils.toBN( 60 ); + optsGasPriceSetup.details.trace( + "Value to subtract from timestamp is {}={}(to adjust it to past a bit)", + bnValueToSubtractFromTimestamp, + owaspUtils.ensureStartsWith0x( bnValueToSubtractFromTimestamp.toHexString() ) ); + optsGasPriceSetup.bnTimestampOfBlock = + optsGasPriceSetup.bnTimestampOfBlock.sub( bnValueToSubtractFromTimestamp ); + optsGasPriceSetup.details.trace( "Timestamp on Main Net is {}={} (adjusted to past a bit)", + optsGasPriceSetup.bnTimestampOfBlock.toHexString(), + owaspUtils.ensureStartsWith0x( optsGasPriceSetup.bnTimestampOfBlock.toHexString() ) ); + optsGasPriceSetup.strActionName = "prepareOracleGasPriceSetup.getGasPrice()"; + optsGasPriceSetup.gasPriceOnMainNet = null; + if( getEnabledOracle() ) { + const oracleOpts = { + url: owaspUtils.ethersProviderToUrl( optsGasPriceSetup.ethersProviderSChain ), + callOpts: { }, + nMillisecondsSleepBefore: 1000, + nMillisecondsSleepPeriod: 3000, + cntAttempts: 40, + isVerbose: + ( log.verboseGet() >= log.verboseName2Number( "information" ) ), + isVerboseTraceDetails: + ( log.verboseGet() >= log.verboseName2Number( "debug" ) ) + }; + optsGasPriceSetup.details.debug( + "Will fetch Main Net gas price via call to Oracle with options {}...", oracleOpts ); + try { + optsGasPriceSetup.gasPriceOnMainNet = owaspUtils.ensureStartsWith0x( + ( await imaOracle.oracleGetGasPrice( + oracleOpts, optsGasPriceSetup.details ) ).toString( 16 ) ); + } catch ( err ) { + optsGasPriceSetup.gasPriceOnMainNet = null; + optsGasPriceSetup.details.error( "Failed to fetch Main Net gas price via call " + + "to Oracle, error is: {err}, stack is:\n{stack}", err, err ); + } + } + if( optsGasPriceSetup.gasPriceOnMainNet === null ) { + optsGasPriceSetup.details.debug( "Will fetch Main Net gas price directly..." ); + optsGasPriceSetup.gasPriceOnMainNet = owaspUtils.ensureStartsWith0x( + owaspUtils.toBN( + await optsGasPriceSetup.ethersProviderMainNet.getGasPrice() ).toHexString() ); + } + optsGasPriceSetup.details.success( "Done, Oracle did computed new Main Net gas price={}={}", + owaspUtils.toBN( optsGasPriceSetup.gasPriceOnMainNet ).toString(), + optsGasPriceSetup.gasPriceOnMainNet ); + const joGasPriceOnMainNetOld = + await optsGasPriceSetup.joCommunityLocker.callStatic.mainnetGasPrice( + { from: optsGasPriceSetup.joAccountSC.address() } ); + const bnGasPriceOnMainNetOld = owaspUtils.toBN( joGasPriceOnMainNetOld ); + optsGasPriceSetup.details.trace( + "Previous Main Net gas price saved and kept in CommunityLocker={}={}", + bnGasPriceOnMainNetOld.toString(), bnGasPriceOnMainNetOld.toHexString() ); + if( bnGasPriceOnMainNetOld.eq( owaspUtils.toBN( optsGasPriceSetup.gasPriceOnMainNet ) ) ) { + optsGasPriceSetup.details.trace( "Previous Main Net gas price is equal to new one, " + + " will skip setting it in CommunityLocker" ); + if( log.exposeDetailsGet() ) { + optsGasPriceSetup.details.exposeDetailsTo( + log.globalStream(), "doOracleGasPriceSetup", true ); + } + optsGasPriceSetup.details.close(); + } +} + +async function handleOracleSigned( + optsGasPriceSetup: TGasPriceSetupOptions, strError: Error | string | null, + u256: any, joGlueResult: any | null ): Promise { + if( strError ) { + optsGasPriceSetup.details.critical( + "{p}Error in doOracleGasPriceSetup() during {bright}: {err}", + optsGasPriceSetup.strLogPrefix, optsGasPriceSetup.strActionName, strError ); + optsGasPriceSetup.details.exposeDetailsTo( + log.globalStream(), "doOracleGasPriceSetup", false ); + imaTransferErrorHandling.saveTransferError( + "oracle", optsGasPriceSetup.details.toString() ); + optsGasPriceSetup.details.close(); + return; + } + optsGasPriceSetup.strActionName = "doOracleGasPriceSetup.formatSignature"; + let signature: owaspUtils.TXYSignature | null = joGlueResult ? joGlueResult.signature : null; + if( !signature ) + signature = { X: "0", Y: "0" }; + let hashPoint = joGlueResult ? joGlueResult.hashPoint : null; + if( !hashPoint ) + hashPoint = { X: "0", Y: "0" }; + let hint = joGlueResult ? joGlueResult.hint : null; + if( !hint ) + hint = "0"; + const sign: owaspUtils.TBLSSignature = { + blsSignature: [ signature.X, signature.Y ], // BLS glue of signatures + hashA: hashPoint.X, // G1.X from joGlueResult.hashSrc + hashB: hashPoint.Y, // G1.Y from joGlueResult.hashSrc + counter: hint + }; + optsGasPriceSetup.strActionName = + "Oracle gas price setup via CommunityLocker.setGasPrice()"; + const arrArgumentsSetGasPrice = [ + u256, + owaspUtils.ensureStartsWith0x( optsGasPriceSetup.bnTimestampOfBlock.toHexString() ), + sign // bls signature components + ]; + const joDebugArgs = [ + [ signature.X, signature.Y ], // BLS glue of signatures + hashPoint.X, // G1.X from joGlueResult.hashSrc + hashPoint.Y, // G1.Y from joGlueResult.hashSrc + hint + ]; + optsGasPriceSetup.details.debug( "{p}....debug args for : {}", + optsGasPriceSetup.strLogPrefix, joDebugArgs ); + const gasPrice = await optsGasPriceSetup.transactionCustomizerSChain.computeGasPrice( + optsGasPriceSetup.ethersProviderSChain, 200000000000 ); + optsGasPriceSetup.details.trace( "{p}Using computed gasPrice={}", + optsGasPriceSetup.strLogPrefix, gasPrice ); + const estimatedGasSetGasPrice = await optsGasPriceSetup.transactionCustomizerSChain.computeGas( + optsGasPriceSetup.details, optsGasPriceSetup.ethersProviderSChain, + "CommunityLocker", optsGasPriceSetup.joCommunityLocker, + "setGasPrice", arrArgumentsSetGasPrice, optsGasPriceSetup.joAccountSC, + optsGasPriceSetup.strActionName, gasPrice, 10000000 ); + optsGasPriceSetup.details.trace( "{p}Using estimated gas={}", + optsGasPriceSetup.strLogPrefix, estimatedGasSetGasPrice ); + const isIgnoreSetGasPrice = false; + const strErrorOfDryRun = await imaTx.dryRunCall( optsGasPriceSetup.details, + optsGasPriceSetup.ethersProviderSChain, + "CommunityLocker", optsGasPriceSetup.joCommunityLocker, + "setGasPrice", arrArgumentsSetGasPrice, + optsGasPriceSetup.joAccountSC, optsGasPriceSetup.strActionName, + isIgnoreSetGasPrice, gasPrice, estimatedGasSetGasPrice ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + const opts: imaTx.TCustomPayedCallOptions = { + isCheckTransactionToSchain: ( optsGasPriceSetup.chainIdSChain !== "Mainnet" ) + }; + const joReceipt = await imaTx.payedCall( optsGasPriceSetup.details, + optsGasPriceSetup.ethersProviderSChain, + "CommunityLocker", optsGasPriceSetup.joCommunityLocker, + "setGasPrice", arrArgumentsSetGasPrice, + optsGasPriceSetup.joAccountSC, optsGasPriceSetup.strActionName, + gasPrice, estimatedGasSetGasPrice, undefined, opts ); + if( joReceipt ) { + optsGasPriceSetup.jarrReceipts.push( { + description: "doOracleGasPriceSetup/setGasPrice", + receipt: joReceipt + } ); + imaGasUsage.printGasUsageReportFromArray( + "(intermediate result) ORACLE GAS PRICE SETUP ", + optsGasPriceSetup.jarrReceipts, optsGasPriceSetup.details ); + } + imaTransferErrorHandling.saveTransferSuccess( "oracle" ); +} + +export async function doOracleGasPriceSetup( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + transactionCustomizerSChain: imaTx.TransactionCustomizer, + joCommunityLocker: owaspUtils.ethersMod.Contract, + joAccountSC: state.TAccount, + chainIdMainNet: string, + chainIdSChain: string, + fnSignMsgOracle: TFunctionSignMsgOracle +): Promise { + if( !getEnabledOracle() ) + return true; + const optsGasPriceSetup: TGasPriceSetupOptions = { + ethersProviderMainNet, + ethersProviderSChain, + transactionCustomizerSChain, + joCommunityLocker, + joAccountSC, + chainIdMainNet, + chainIdSChain, + fnSignMsgOracle, + details: log.createMemoryStream(), + jarrReceipts: [], + strLogPrefix: "Oracle gas price setup: ", + strActionName: "", + latestBlockNumber: null, + latestBlock: null, + bnTimestampOfBlock: null, + bnTimeZoneOffset: null, + gasPriceOnMainNet: null + }; + if( optsGasPriceSetup.fnSignMsgOracle == null || + optsGasPriceSetup.fnSignMsgOracle == undefined ) { + optsGasPriceSetup.details.trace( "{p}Using internal u256 signing stub function", + optsGasPriceSetup.strLogPrefix ); + optsGasPriceSetup.fnSignMsgOracle = + async function( u256: any, details: log.TLogger, + fnAfter: IMA.TFunctionAfterSigningMessages ): Promise { + details.trace( "{p}u256 signing callback was not provided", + optsGasPriceSetup.strLogPrefix ); + await fnAfter( null, u256, null ); // null - no error, null - no signatures + }; + } else { + optsGasPriceSetup.details.trace( "{p}Using externally provided u256 signing function", + optsGasPriceSetup.strLogPrefix ); + } + try { + await prepareOracleGasPriceSetup( optsGasPriceSetup ); + optsGasPriceSetup.strActionName = + "doOracleGasPriceSetup.optsGasPriceSetup.fnSignMsgOracle()"; + await optsGasPriceSetup.fnSignMsgOracle( + optsGasPriceSetup.gasPriceOnMainNet, optsGasPriceSetup.details, + async function( + strError: Error | string | null, u256: any, joGlueResult: any | null + ): Promise { + await handleOracleSigned( optsGasPriceSetup, strError, u256, joGlueResult ); + } ); + } catch ( err ) { + optsGasPriceSetup.details.critical( + "{p}Error in doOracleGasPriceSetup() during {bright}: {err}, stack is:\n{stack}", + optsGasPriceSetup.strLogPrefix, optsGasPriceSetup.strActionName, + err, err ); + optsGasPriceSetup.details.exposeDetailsTo( + log.globalStream(), "doOracleGasPriceSetup", false ); + imaTransferErrorHandling.saveTransferError( + "oracle", optsGasPriceSetup.details.toString() ); + optsGasPriceSetup.details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( "ORACLE GAS PRICE SETUP ", + optsGasPriceSetup.jarrReceipts, optsGasPriceSetup.details ); + if( log.exposeDetailsGet() ) { + optsGasPriceSetup.details.exposeDetailsTo( + log.globalStream(), "doOracleGasPriceSetup", true ); + } + optsGasPriceSetup.details.close(); + return true; +} diff --git a/src/imaRegistrationOperations.ts b/src/imaRegistrationOperations.ts new file mode 100644 index 00000000..fbd9664e --- /dev/null +++ b/src/imaRegistrationOperations.ts @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaRegistrationOperations.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaTx from "./imaTx.js"; +import * as threadInfo from "./threadInfo.js"; +import type * as owaspUtils from "./owaspUtils.js"; +import type * as state from "./state.js"; + +export async function invokeHasChain( + details: log.TLogger, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, // Main-Net or S-Chin + joLinker: owaspUtils.ethersMod.Contract, // Main-Net or S-Chin + joAccount: state.TAccount, // Main-Net or S-Chin + chainIdSChain: string +): Promise { + const strLogPrefix = "Wait for added chain status: "; + const strActionName = "invokeHasChain(hasSchain): joLinker.hasSchain"; + try { + details.debug( "{p}Will call {bright}...", strLogPrefix, strActionName ); + const addressFrom = joAccount.address(); + const bHasSchain = + await joLinker.callStatic.hasSchain( chainIdSChain, { from: addressFrom } ); + details.success( "{p}Got joLinker.hasSchain() status is: {}", strLogPrefix, bHasSchain ); + return bHasSchain; + } catch ( err ) { + details.critical( "{p}Error in invokeHasChain() during {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + } + return false; +} + +export async function waitForHasChain( + details: log.TLogger, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, // Main-Net or S-Chin + joLinker: owaspUtils.ethersMod.Contract, // Main-Net or S-Chin + joAccount: state.TAccount, // Main-Net or S-Chin + chainIdSChain: string, + cntWaitAttempts?: number, + nSleepMilliseconds?: number +): Promise { + if( !cntWaitAttempts ) + cntWaitAttempts = 100; + if( !nSleepMilliseconds ) + nSleepMilliseconds = 5; + for( let idxWaitAttempts = 0; idxWaitAttempts < cntWaitAttempts; ++idxWaitAttempts ) { + if( await invokeHasChain( details, ethersProvider, joLinker, joAccount, chainIdSChain ) ) + return true; + details.trace( "Sleeping {} milliseconds...", nSleepMilliseconds ); + await threadInfo.sleep( nSleepMilliseconds ); + } + return false; +} + +// +// register direction for money transfer +// main-net.DepositBox call: function addSchain(string schainName, address tokenManagerAddress) +// +export async function checkIsRegisteredSChainInDepositBoxes( // step 1 + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joLinker: owaspUtils.ethersMod.Contract | null, + joAccountMN: state.TAccount, + chainIdSChain: string +): Promise { + const details = log.createMemoryStream(); + details.debug( "Main-net Linker address is...........{}", joLinker ? joLinker.address : "N/A" ); + details.debug( "S-Chain ID is.......................{}", chainIdSChain ); + const strLogPrefix = "RegChk S in depositBox: "; + details.debug( "{p}{p}", strLogPrefix, imaHelperAPIs.longSeparator ); + details.debug( "{p}{p}", strLogPrefix, "checkIsRegisteredSChainInDepositBoxes(reg-step1)" ); + details.debug( "{p}{p}", strLogPrefix, imaHelperAPIs.longSeparator ); + let strActionName = ""; + try { + strActionName = "checkIsRegisteredSChainInDepositBoxes(reg-step1)"; + const addressFrom = joAccountMN.address(); + if( !joLinker ) + throw new Error( "No Linker contract" ); + const bIsRegistered = + await joLinker.callStatic.hasSchain( chainIdSChain, { from: addressFrom } ); + details.success( "{p}checkIsRegisteredSChainInDepositBoxes(reg-step1) status is: {}", + strLogPrefix, bIsRegistered ); + if( log.exposeDetailsGet() ) { + details.exposeDetailsTo( + log.globalStream(), "checkIsRegisteredSChainInDepositBoxes", true ); + } + details.close(); + return bIsRegistered; + } catch ( err ) { + details.critical( + "{p}Error in checkIsRegisteredSChainInDepositBoxes(reg-step1)() during {bright}: " + + "{err}, stack is:\n{stack}", strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( + log.globalStream(), "checkIsRegisteredSChainInDepositBoxes", false ); + details.close(); + } + return false; +} + +export async function registerSChainInDepositBoxes( // step 1 + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joLinker: owaspUtils.ethersMod.ethers.Contract | null, + joAccountMN: state.TAccount, + joTokenManagerETH: owaspUtils.ethersMod.ethers.Contract | null, // only s-chain + joTokenManagerERC20: owaspUtils.ethersMod.ethers.Contract | null, // only s-chain + joTokenManagerERC721: owaspUtils.ethersMod.ethers.Contract | null, // only s-chain + joTokenManagerERC1155: owaspUtils.ethersMod.ethers.Contract | null, // only s-chain + joTokenManagerERC721WithMetadata: owaspUtils.ethersMod.ethers.Contract | null, // only s-chain + joCommunityLocker: owaspUtils.ethersMod.ethers.Contract | null, // only s-chain + joTokenManagerLinker: owaspUtils.ethersMod.ethers.Contract | null, + chainNameSChain: string, + chainNameMainNet: string, + transactionCustomizerMainNet: imaTx.TransactionCustomizer, + cntWaitAttempts?: number, + nSleepMilliseconds?: number +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any[] = []; + details.debug( "Main-net Linker address is..........{}", joLinker ? joLinker.address : "N/A" ); + details.debug( "S-Chain ID is.......................{}", chainNameSChain ); + const strLogPrefix = "Reg S in depositBoxes: "; + details.debug( "{p}{}", strLogPrefix, imaHelperAPIs.longSeparator ); + details.debug( "{p}reg-step1:registerSChainInDepositBoxes", strLogPrefix ); + details.debug( "{p}{}", strLogPrefix, imaHelperAPIs.longSeparator ); + let strActionName = ""; + try { + strActionName = "Register S-chain in deposit boxes, step 1, connectSchain"; + details.debug( "{p}Will register S-Chain in lock_and_data on Main-net", strLogPrefix ); + if( !joTokenManagerLinker ) + throw new Error( "No TokenManagerLinker contract" ); + if( !joCommunityLocker ) + throw new Error( "No CommunityLocker contract" ); + if( !joTokenManagerETH ) + throw new Error( "No TokenManagerETH contract" ); + if( !joTokenManagerERC20 ) + throw new Error( "No TokenManagerERC20 contract" ); + if( !joTokenManagerERC721 ) + throw new Error( "No TokenManagerERC721 contract" ); + if( !joTokenManagerERC1155 ) + throw new Error( "No TokenManagerERC1155 contract" ); + if( !joTokenManagerERC721WithMetadata ) + throw new Error( "No TokenManagerERC721WithMetadata contract" ); + if( !joLinker ) + throw new Error( "No Linker contract" ); + const arrArguments = [ + chainNameSChain, [ + joTokenManagerLinker.address, // call params + joCommunityLocker.address, // call params + joTokenManagerETH.address, // call params + joTokenManagerERC20.address, // call params + joTokenManagerERC721.address, // call params + joTokenManagerERC1155.address, // call params + joTokenManagerERC721WithMetadata.address // call params + ] ]; + const gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGas = await transactionCustomizerMainNet.computeGas( details, + ethersProviderMainNet, "Linker", joLinker, "connectSchain", arrArguments, + joAccountMN, strActionName, gasPrice, 3000000 ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGas ); + const isIgnore = false; + const strErrorOfDryRun = await imaTx.dryRunCall( details, ethersProviderMainNet, + "Linker", joLinker, "connectSchain", arrArguments, + joAccountMN, strActionName, isIgnore, + gasPrice, estimatedGas ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const joReceipt = await imaTx.payedCall( + details, ethersProviderMainNet, + "Linker", joLinker, "connectSchain", arrArguments, + joAccountMN, strActionName, + gasPrice, estimatedGas ); + if( joReceipt ) { + jarrReceipts.push( { + description: "registerSChainInDepositBoxes", + receipt: joReceipt + } ); + } + const isSChainStatusOKay = await waitForHasChain( + details, ethersProviderMainNet, + joLinker, joAccountMN, chainNameSChain, + cntWaitAttempts, nSleepMilliseconds ); + if( !isSChainStatusOKay ) + throw new Error( "S-Chain ownership status check timeout" ); + } catch ( err ) { + details.critical( "{p}Error in registerSChainInDepositBoxes() during {bright}: {err}" + + ", stack is:\n{stack}", strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( + log.globalStream(), "registerSChainInDepositBoxes", false ); + details.close(); + return null; + } + if( log.exposeDetailsGet() ) { + details.exposeDetailsTo( + log.globalStream(), "registerSChainInDepositBoxes", true ); + } + details.close(); + return jarrReceipts; +} diff --git a/src/imaReimbursementOperations.ts b/src/imaReimbursementOperations.ts new file mode 100644 index 00000000..c1cc66b3 --- /dev/null +++ b/src/imaReimbursementOperations.ts @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaReimbursementOperations.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as imaTx from "./imaTx.js"; +import * as imaGasUsage from "./imaGasUsageOperations.js"; +import type * as state from "./state.js"; + +export async function reimbursementShowBalance( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joCommunityPool: owaspUtils.ethersMod.Contract, + joReceiverMainNet: any, + strChainNameMainNet: string, + chainIdMainNet: string, + transactionCustomizerMainNet: imaTx.TransactionCustomizer, + strReimbursementChain: string, + isForcePrintOut: boolean +): Promise { + const details = log.createMemoryStream(); + let s = ""; + const strLogPrefix = "Gas Reimbursement - Show Balance "; + try { + const addressFrom = joReceiverMainNet; + details.debug( "{p}Querying wallet {}/{} balance...", + strLogPrefix, strReimbursementChain, addressFrom ); + const xWei = await joCommunityPool.callStatic.getBalance( + addressFrom, strReimbursementChain, { from: addressFrom } ); + s = strLogPrefix + log.fmtSuccess( "Balance(wei): {}", xWei ); + if( isForcePrintOut ) + log.information( s ); + details.information( s ); + const xEth = owaspUtils.ethersMod.ethers.utils.formatEther( owaspUtils.toBN( xWei ) ); + s = strLogPrefix + log.fmtSuccess( "Balance(eth): {}", xEth ); + if( isForcePrintOut ) + log.information( s ); + details.information( s ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "reimbursementShowBalance", true ); + details.close(); + return xWei; + } catch ( err ) { + details.critical( "{p}Payment error in reimbursementShowBalance(): {err}, " + + "stack is:\n{stack}", strLogPrefix, err, err ); + details.exposeDetailsTo( log.globalStream(), "reimbursementShowBalance", false ); + details.close(); + return 0; + } +} + +export async function reimbursementEstimateAmount( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joCommunityPool: owaspUtils.ethersMod.Contract, + joReceiverMainNet: any, + strChainNameMainNet: string, + chainIdMainNet: string, + transactionCustomizerMainNet: imaTx.TransactionCustomizer, + strReimbursementChain: string, + isForcePrintOut: boolean +): Promise { + const details = log.createMemoryStream(); + let s = ""; + const strLogPrefix = "Gas Reimbursement - Estimate Amount To Recharge "; + try { + details.debug( "{p}Querying wallet {} balance...", + strLogPrefix, strReimbursementChain ); + const addressReceiver = joReceiverMainNet; + const xWei = + await joCommunityPool.callStatic.getBalance( + addressReceiver, strReimbursementChain, { from: addressReceiver } ); + s = strLogPrefix + log.fmtSuccess( "Balance(wei): {}", xWei ); + if( isForcePrintOut ) + log.information( s ); + details.information( s ); + const xEth = owaspUtils.ethersMod.ethers.utils.formatEther( owaspUtils.toBN( xWei ) ); + s = strLogPrefix + log.fmtSuccess( "Balance(eth): {}", xEth ); + if( isForcePrintOut ) + log.information( s ); + details.information( s ); + const minTransactionGas = owaspUtils.parseIntOrHex( + await joCommunityPool.callStatic.minTransactionGas( { from: addressReceiver } ) ); + s = strLogPrefix + log.fmtSuccess( "MinTransactionGas: {}", minTransactionGas ); + if( isForcePrintOut ) + log.information( s ); + details.information( s ); + + const gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + s = strLogPrefix + log.fmtSuccess( "Multiplied Gas Price: {}", gasPrice ); + if( isForcePrintOut ) + log.information( s ); + details.information( s ); + + const minAmount = minTransactionGas * gasPrice; + s = strLogPrefix + log.fmtSuccess( "Minimum recharge balance: {}", minAmount ); + if( isForcePrintOut ) + log.information( s ); + details.information( s ); + + let amountToRecharge = 0; + if( xWei >= minAmount ) + amountToRecharge = 1; + else + amountToRecharge = minAmount - xWei; + + s = strLogPrefix + log.fmtSuccess( "Estimated amount to recharge(wei): {}", + amountToRecharge ); + if( isForcePrintOut ) + log.information( s ); + details.information( s ); + + const amountToRechargeEth = + owaspUtils.ethersMod.ethers.utils.formatEther( + owaspUtils.toBN( amountToRecharge.toString() ) ); + s = strLogPrefix + log.fmtSuccess( "Estimated amount to recharge(eth): {}", + amountToRechargeEth ); + if( isForcePrintOut ) + log.information( s ); + details.information( s ); + + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "reimbursementEstimateAmount", true ); + details.close(); + return amountToRecharge; + } catch ( err ) { + details.critical( "{p} Payment error in reimbursementEstimateAmount(): {err}, " + + "stack is:\n{stack}", strLogPrefix, err, err ); + details.exposeDetailsTo( log.globalStream(), "reimbursementEstimateAmount", false ); + details.close(); + return 0; + } +} + +export async function reimbursementWalletRecharge( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joCommunityPool: owaspUtils.ethersMod.Contract, + joAccountMN: state.TAccount, + strChainNameMainNet: string, + chainIdMainNet: string, + transactionCustomizerMainNet: imaTx.TransactionCustomizer, + strReimbursementChain: string, + nReimbursementRecharge: string | number | null +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "Gas Reimbursement - Wallet Recharge "; + try { + details.debug( "{p}Recharging wallet {}...", + strLogPrefix, strReimbursementChain ); + strActionName = "Recharge reimbursement wallet on Main Net"; + const addressReceiver = joAccountMN.address(); + const arrArguments = [ strReimbursementChain, addressReceiver ]; + const gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGas = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "CommunityPool", joCommunityPool, "rechargeUserWallet", arrArguments, + joAccountMN, strActionName, gasPrice, 3000000, nReimbursementRecharge, null ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGas ); + const isIgnore = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProviderMainNet, + "CommunityPool", joCommunityPool, "rechargeUserWallet", arrArguments, + joAccountMN, strActionName, isIgnore, + gasPrice, estimatedGas, nReimbursementRecharge, null ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const joReceipt = await imaTx.payedCall( + details, ethersProviderMainNet, + "CommunityPool", joCommunityPool, "rechargeUserWallet", arrArguments, + joAccountMN, strActionName, gasPrice, estimatedGas, nReimbursementRecharge, null ); + if( joReceipt ) { + jarrReceipts.push( { + description: "reimbursementWalletRecharge", + receipt: joReceipt + } ); + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "reimbursementWalletRecharge", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "REIMBURSEMENT_WALLET_RECHARGE", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "reimbursementWalletRecharge", true ); + details.close(); + return true; +} + +export async function reimbursementWalletWithdraw( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joCommunityPool: owaspUtils.ethersMod.Contract, + joAccountMN: state.TAccount, + strChainNameMainNet: string, + chainIdMainNet: string, + transactionCustomizerMainNet: imaTx.TransactionCustomizer, + strReimbursementChain: string, + nReimbursementWithdraw: string | number | null +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "Gas Reimbursement - Wallet Withdraw "; + try { + details.debug( "{p}Withdrawing wallet {}...", + strLogPrefix, strReimbursementChain ); + strActionName = "Withdraw reimbursement wallet"; + const arrArguments = [ + strReimbursementChain, + owaspUtils.ensureStartsWith0x( + owaspUtils.toBN( nReimbursementWithdraw ).toHexString() ) + ]; + const gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGas = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "CommunityPool", joCommunityPool, "withdrawFunds", arrArguments, + joAccountMN, strActionName, gasPrice, 3000000 ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGas ); + const isIgnore = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProviderMainNet, + "CommunityPool", joCommunityPool, "withdrawFunds", arrArguments, + joAccountMN, strActionName, isIgnore, + gasPrice, estimatedGas ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const joReceipt = await imaTx.payedCall( + details, ethersProviderMainNet, + "CommunityPool", joCommunityPool, "withdrawFunds", arrArguments, + joAccountMN, strActionName, + gasPrice, estimatedGas ); + if( joReceipt ) { + jarrReceipts.push( { + description: "reimbursementWalletWithdraw", + receipt: joReceipt + } ); + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "reimbursementWalletWithdraw", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "REIMBURSEMENT_WALLET_WITHDRAW", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "reimbursementWalletWithdraw", true ); + details.close(); + return true; +} + +export async function reimbursementSetRange( + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joCommunityLocker: owaspUtils.ethersMod.Contract, + joAccountSC: state.TAccount, + strChainNameSChain: string, + chainIdSChain: string, + transactionCustomizerSChain: imaTx.TransactionCustomizer, + strChainNameOriginChain: string, + nReimbursementRange: any +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "Gas Reimbursement - Set Minimal time interval from S2M transfers "; + try { + details.debug( "{p}Setting minimal S2M interval to {}...", + strLogPrefix, nReimbursementRange ); + strActionName = "Set reimbursement range"; + const arrArguments = [ + strChainNameOriginChain, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( nReimbursementRange ).toHexString() ) + ]; + const gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGas = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "CommunityLocker", joCommunityLocker, "setTimeLimitPerMessage", arrArguments, + joAccountSC, strActionName, gasPrice, 3000000 ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGas ); + const isIgnore = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProviderSChain, + "CommunityLocker", joCommunityLocker, "setTimeLimitPerMessage", arrArguments, + joAccountSC, strActionName, isIgnore, gasPrice, estimatedGas ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const opts: imaTx.TCustomPayedCallOptions = { isCheckTransactionToSchain: true }; + const joReceipt = await imaTx.payedCall( + details, ethersProviderSChain, + "CommunityLocker", joCommunityLocker, "setTimeLimitPerMessage", arrArguments, + joAccountSC, strActionName, gasPrice, estimatedGas, undefined, opts ); + if( joReceipt ) { + jarrReceipts.push( { + description: "reimbursementSetRange", + receipt: joReceipt + } ); + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "reimbursementSetRange", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "REIMBURSEMENT_SET_RANGE", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "reimbursementSetRange", true ); + details.close(); + return true; +} diff --git a/src/imaSgxExternalSigner.ts b/src/imaSgxExternalSigner.ts new file mode 100644 index 00000000..0de23e31 --- /dev/null +++ b/src/imaSgxExternalSigner.ts @@ -0,0 +1,158 @@ +import * as fs from "fs"; +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as rpcCall from "./rpcCall.js"; + +const gIsDebugLogging = false; // development option only, must be always false +log.addStdout(); + +// allow self-signed wss and https +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +function finalizeOutput( jo: any ): void { + if( !jo ) + return; + process.stdout.write( log.fmtInformation( "{}", jo ) ); +} + +function postConvertBN( jo: any, name: any ): void { + if( !jo ) + return; + if( !( name in jo ) ) + return; + if( typeof jo[name] !== "object" ) + return; + jo[name] = owaspUtils.toHexStringSafe( jo[name] ); +} + +async function run(): Promise { + try { + if( gIsDebugLogging ) + log.debug( "Process startup arguments array is {}", process.argv ); + + const strSgxWalletURL = process.argv[3]; + if( gIsDebugLogging ) + log.debug( "SGX Wallet URL is {url}", strSgxWalletURL ); + const strSgxKeyName = process.argv[4]; + if( gIsDebugLogging ) + log.debug( "SGX key name is {url}", strSgxWalletURL ); + const strURL = process.argv[5]; + if( gIsDebugLogging ) + log.debug( "Chain URL is {url}", strURL ); + const chainId = process.argv[6]; + if( gIsDebugLogging ) + log.debug( "Chain ID is {}", chainId ); + const tcData = process.argv[7]; + if( gIsDebugLogging ) + log.debug( "TX data is {}", tcData ); + const txTo = process.argv[8]; + if( gIsDebugLogging ) + log.debug( "TX destination is {}", txTo ); + const txValue = process.argv[9]; + if( gIsDebugLogging ) + log.debug( "TX value is {}", txValue ); + const gasPrice = process.argv[10]; + if( gIsDebugLogging ) + log.debug( "TX gas price is {}", gasPrice ); + const gasLimit = process.argv[11]; + if( gIsDebugLogging ) + log.debug( "TX gas limit is {}", gasLimit ); + const txNonce = process.argv[12]; + if( gIsDebugLogging ) + log.debug( "TX nonce is {}", txNonce ); + const strPathCert = process.argv[13]; + if( gIsDebugLogging ) + log.debug( "Path to SGX certificate file is {}", strPathCert ); + + const strPathKey = process.argv[14]; + if( gIsDebugLogging ) + log.debug( "Path to SGX key file is {}", strPathKey ); + const ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider = + owaspUtils.getEthersProviderFromURL( strURL ); + const tx: any = { + data: tcData, + to: txTo, + value: owaspUtils.toBN( txValue ), + chainId: owaspUtils.parseIntOrHex( chainId ), + gasPrice: owaspUtils.toBN( gasPrice ), + gasLimit: owaspUtils.toBN( gasLimit ), + nonce: owaspUtils.toBN( txNonce ) + }; + if( gIsDebugLogging ) + log.debug( "--- Source TX ---> {}", tx ); + let rawTX = owaspUtils.ethersMod.ethers.utils.serializeTransaction( tx ); + if( gIsDebugLogging ) + log.debug( "--- RAW unsigned TX ---> {}", rawTX ); + const txHash = owaspUtils.ethersMod.ethers.utils.keccak256( rawTX ); + if( gIsDebugLogging ) + log.debug( "--- TX hash ---> {}", txHash ); + + const rpcCallOpts: rpcCall.TRPCCallOpts | null = { + cert: fs.readFileSync( strPathCert, "utf8" ), + key: fs.readFileSync( strPathKey, "utf8" ) + }; + + const joCall: rpcCall.TRPCCall = await rpcCall.create( strSgxWalletURL, rpcCallOpts ); + if( !joCall ) + throw new Error( `Failed to create JSON RPC call object to ${strSgxWalletURL}` ); + const joIn: any = { + method: "ecdsaSignMessageHash", + params: { + keyName: strSgxKeyName.toString(), + messageHash: txHash, + base: 16 + } + }; + const joOut: any = await joCall.call( joIn ); + try { + if( gIsDebugLogging ) + log.debug( "SGX wallet ECDSA sign result is: {}", joOut ); + + const v = parseInt( joOut.result.signature_v ); + const ethV = v + owaspUtils.parseIntOrHex( chainId ) * 2 + 35; + const joExpanded = { + recoveryParam: v, + v: ethV, + r: joOut.result.signature_r, + s: joOut.result.signature_s + }; + if( gIsDebugLogging ) + log.debug( "--- Expanded signature ---> {}", joExpanded ); + rawTX = owaspUtils.ethersMod.ethers.utils + .serializeTransaction( tx, joExpanded ); + if( gIsDebugLogging ) + log.debug( "--- Raw transaction with signature ---> {}", rawTX ); + + const sr = await ethersProvider.sendTransaction( rawTX ); + if( gIsDebugLogging ) + log.debug( "--- Raw-sent transaction result ---> {}", sr ); + + const joReceipt: any = await ethersProvider.waitForTransaction( sr.hash ); + if( gIsDebugLogging ) + log.debug( "--- Transaction receipt ---> {}", joReceipt ); + joReceipt.chainId = tx.chainId; + joReceipt.rawTX = rawTX; + joReceipt.signature = joExpanded; + postConvertBN( joReceipt, "gasUsed" ); + postConvertBN( joReceipt, "cumulativeGasUsed" ); + postConvertBN( joReceipt, "effectiveGasPrice" ); + if( joReceipt.error ) { + finalizeOutput( joReceipt ); + process.exit( 1 ); + } + finalizeOutput( joReceipt ); + process.exit( 0 ); + } catch ( err ) { + if( gIsDebugLogging ) + log.debug( "--- Call error ---> {}", log.em, ( err ) ); + finalizeOutput( { error: owaspUtils.extractErrorMessage( err ) } ); + process.exit( 1 ); + } + } catch ( err ) { + if( gIsDebugLogging ) + log.error( "RPC call to SGX failed: {err}", err ); + finalizeOutput( { error: owaspUtils.extractErrorMessage( err ) } ); + process.exit( 1 ); + } +} +run().then( function(): void {} ).catch( function(): void {} ); diff --git a/src/imaTokenOperations.ts b/src/imaTokenOperations.ts new file mode 100644 index 00000000..34f080e2 --- /dev/null +++ b/src/imaTokenOperations.ts @@ -0,0 +1,2252 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaTokenOperations.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaTx from "./imaTx.js"; +import * as imaGasUsage from "./imaGasUsageOperations.js"; +import * as imaEventLogScan from "./imaEventLogScan.js"; +import * as threadInfo from "./threadInfo.js"; +import type * as state from "./state.js"; + +export async function getBalanceErc20( + isMainNet: boolean, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainId: string, + joAccount: state.TAccount, + strCoinName: string, + joABI: any +): Promise { + const strLogPrefix = "getBalanceErc20() call "; + try { + if( !( ethersProvider && joAccount && strCoinName && joABI && ( strCoinName + "_abi" ) in + joABI && ( strCoinName + "_address" ) in joABI ) ) + return ""; + const strAddress = joAccount.address(); + const contractERC20 = new owaspUtils.ethersMod.ethers.Contract( + joABI[strCoinName + "_address"], + joABI[strCoinName + "_abi"], + ethersProvider + ); + const balance = + await contractERC20.callStatic.balanceOf( strAddress, { from: strAddress } ); + return balance; + } catch ( err ) { + log.error( "{p}ERC20 balance fetching error: {err}, stack is:\n{stack}", + strLogPrefix, err, err ); + } + return ""; +} + +export async function getOwnerOfErc721( + isMainNet: boolean, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainId: string, + joAccount: state.TAccount, + strCoinName: string, + joABI: any, + idToken: any +): Promise { + const strLogPrefix = "getOwnerOfErc721() call "; + try { + if( !( ethersProvider && joAccount && strCoinName && joABI && ( strCoinName + "_abi" ) in + joABI && ( strCoinName + "_address" ) in joABI ) ) + return ""; + const strAddress = joAccount.address(); + const contractERC721 = new owaspUtils.ethersMod.ethers.Contract( + joABI[strCoinName + "_address"], + joABI[strCoinName + "_abi"], + ethersProvider + ); + const owner = await contractERC721.callStatic.ownerOf( idToken, { from: strAddress } ); + return owner; + } catch ( err ) { + log.error( "{p}ERC721 owner fetching error: {err}, stack is:\n{stack}", + strLogPrefix, err, err ); + } + return ""; +} + +export async function getBalanceErc1155( + isMainNet: boolean, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainId: string, + joAccount: state.TAccount, + strCoinName: string, + joABI: any, + idToken: any +): Promise { + const strLogPrefix = "getBalanceErc1155() call "; + try { + if( !( ethersProvider && joAccount && strCoinName && joABI && ( strCoinName + "_abi" ) in + joABI && ( strCoinName + "_address" ) in joABI ) ) + return ""; + const strAddress = joAccount.address(); + const contractERC1155 = new owaspUtils.ethersMod.ethers.Contract( + joABI[strCoinName + "_address"], + joABI[strCoinName + "_abi"], + ethersProvider + ); + const balance = await contractERC1155.callStatic.balanceOf( + strAddress, idToken, { from: strAddress } ); + return balance; + } catch ( err ) { + log.error( "{p}ERC1155 balance fetching error: {err}, stack is:\n{stack}", + strLogPrefix, err, err ); + } + return ""; +} + +export async function doErc721PaymentFromMainNet( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, + chainIdSChain: string, + joAccountSrc: state.TAccount, + joAccountDst: state.TAccount, + joDepositBoxERC721: owaspUtils.ethersMod.ethers.Contract, + joMessageProxyMainNet: owaspUtils.ethersMod.ethers.Contract, // for checking logs + chainNameSChain: string, + tokenId: any, // which ERC721 token id to send + weiHowMuch: any, // how much ETH + joTokenManagerERC721: owaspUtils.ethersMod.ethers.Contract, // only s-chain + strCoinNameErc721MainNet: string, + erc721PrivateTestnetJsonMainNet: any, + strCoinNameErc721SChain: string, + erc721PrivateTestnetJsonSChain: any, + transactionCustomizerMainNet: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "M2S ERC721 Payment: "; + try { + strActionName = "ERC721 payment from Main Net, approve"; + const erc721ABI = erc721PrivateTestnetJsonMainNet[strCoinNameErc721MainNet + "_abi"]; + const erc721AddressMainNet = + erc721PrivateTestnetJsonMainNet[strCoinNameErc721MainNet + "_address"]; + const contractERC721 = new owaspUtils.ethersMod.ethers.Contract( + erc721AddressMainNet, erc721ABI, ethersProviderMainNet ); + const depositBoxAddress = joDepositBoxERC721.address; + const arrArgumentsApprove = [ + depositBoxAddress, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenId ).toHexString() ) + ]; + const arrArgumentsDepositERC721 = [ + chainNameSChain, + erc721AddressMainNet, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenId ).toHexString() ) + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "ERC721", contractERC721, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(approve) gas={}", strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderMainNet, + "ERC721", contractERC721, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderMainNet, + "ERC721", contractERC721, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: "doErc721PaymentFromMainNet/approve", + receipt: joReceiptApprove + } ); + } + + strActionName = "ERC721 payment from Main Net, depositERC721"; + const weiHowMuchDepositERC721 = undefined; + gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasDeposit = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "DepositBoxERC721", joDepositBoxERC721, "depositERC721", arrArgumentsDepositERC721, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchDepositERC721, null ); + details.trace( "{p}Using estimated(deposit) gas={}", strLogPrefix, estimatedGasDeposit ); + const isIgnoreDepositERC721 = true; + const strErrorOfDryRunDepositERC721 = await imaTx.dryRunCall( + details, ethersProviderMainNet, + "DepositBoxERC721", joDepositBoxERC721, + "depositERC721", arrArgumentsDepositERC721, + joAccountSrc, strActionName, isIgnoreDepositERC721, + gasPrice, estimatedGasDeposit, weiHowMuchDepositERC721, null ); + if( strErrorOfDryRunDepositERC721 ) + throw new Error( strErrorOfDryRunDepositERC721 ); + + const joReceiptDeposit = await imaTx.payedCall( + details, ethersProviderMainNet, + "DepositBoxERC721", joDepositBoxERC721, "depositERC721", arrArgumentsDepositERC721, + joAccountSrc, strActionName, gasPrice, estimatedGasDeposit, + weiHowMuchDepositERC721, null ); + if( joReceiptDeposit ) { + jarrReceipts.push( { + description: "doErc721PaymentFromMainNet/deposit", + receipt: joReceiptDeposit + } ); + } + + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxyMainNet ) { + details.debug( "{p}Verifying the {} event of the MessageProxy/{} contract...", + strLogPrefix, strEventName, joMessageProxyMainNet.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderMainNet, joMessageProxyMainNet, strEventName, + joReceiptDeposit.blockNumber, joReceiptDeposit.transactionHash, + joMessageProxyMainNet.filters[strEventName]() ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{}" + + " contract, found event(s): {}", strLogPrefix, strEventName, + joMessageProxyMainNet.address, joEvents ); + } else { + throw new Error( "Verification failed for the OutgoingMessage event of the " + + `MessageProxy ${joMessageProxyMainNet.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doErc721PaymentFromMainNet", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "ERC-721 PAYMENT FROM MAIN NET", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doErc721PaymentFromMainNet", true ); + details.close(); + return true; +} + +export async function doErc20PaymentFromMainNet( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, + chainIdSChain: string, + joAccountSrc: state.TAccount, + joAccountDst: state.TAccount, + joDepositBoxERC20: owaspUtils.ethersMod.ethers.Contract, + joMessageProxyMainNet: owaspUtils.ethersMod.ethers.Contract, // for checking logs + chainNameSChain: string, + tokenAmount: any, // how much ERC20 tokens to send + weiHowMuch: any, // how much ETH + joTokenManagerERC20: owaspUtils.ethersMod.ethers.Contract, // only s-chain + strCoinNameErc20MainNet: string, + erc20MainNet: any, + strCoinNameErc20SChain: string, + erc20SChain: any, + transactionCustomizerMainNet: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "M2S ERC20 Payment: "; + try { + strActionName = "ERC20 payment from Main Net, approve"; + const erc20ABI = erc20MainNet[strCoinNameErc20MainNet + "_abi"]; + const erc20AddressMainNet = + erc20MainNet[strCoinNameErc20MainNet + "_address"]; + const contractERC20 = new owaspUtils.ethersMod.ethers.Contract( + erc20AddressMainNet, erc20ABI, ethersProviderMainNet ); + const depositBoxAddress = joDepositBoxERC20.address; + const arrArgumentsApprove = [ + depositBoxAddress, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenAmount ).toHexString() ) + ]; + const arrArgumentsDepositERC20 = [ + chainNameSChain, + erc20AddressMainNet, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenAmount ).toHexString() ) + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "ERC20", contractERC20, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(approve) gas={}", strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderMainNet, + "ERC20", contractERC20, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderMainNet, + "ERC20", contractERC20, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: "doErc20PaymentFromMainNet/approve", + receipt: joReceiptApprove + } ); + } + + strActionName = "ERC20 payment from Main Net, depositERC20"; + const weiHowMuchDepositERC20 = undefined; + gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasDeposit = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "DepositBoxERC20", joDepositBoxERC20, "depositERC20", arrArgumentsDepositERC20, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchDepositERC20, null ); + details.trace( "{p}Using estimated(deposit) gas={}", strLogPrefix, estimatedGasDeposit ); + const isIgnoreDepositERC20 = true; + const strErrorOfDryRunDepositERC20 = await imaTx.dryRunCall( + details, ethersProviderMainNet, + "DepositBoxERC20", joDepositBoxERC20, "depositERC20", arrArgumentsDepositERC20, + joAccountSrc, strActionName, isIgnoreDepositERC20, + gasPrice, estimatedGasDeposit, weiHowMuchDepositERC20, null ); + if( strErrorOfDryRunDepositERC20 ) + throw new Error( strErrorOfDryRunDepositERC20 ); + + const joReceiptDeposit = await imaTx.payedCall( + details, ethersProviderMainNet, + "DepositBoxERC20", joDepositBoxERC20, "depositERC20", arrArgumentsDepositERC20, + joAccountSrc, strActionName, gasPrice, estimatedGasDeposit, + weiHowMuchDepositERC20, null ); + if( joReceiptDeposit ) { + jarrReceipts.push( { + description: "doErc20PaymentFromMainNet/deposit", + receipt: joReceiptDeposit + } ); + } + + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxyMainNet ) { + details.debug( "{p}Verifying the {} event of the MessageProxy/{} contract...", + strLogPrefix, strEventName, joMessageProxyMainNet.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderMainNet, joMessageProxyMainNet, strEventName, + joReceiptDeposit.blockNumber, joReceiptDeposit.transactionHash, + joMessageProxyMainNet.filters[strEventName]() ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{}" + + " contract, found event(s): {}", strLogPrefix, strEventName, + joMessageProxyMainNet.address, joEvents ); + } else { + throw new Error( "Verification failed for the OutgoingMessage event of the " + + `MessageProxy ${joMessageProxyMainNet.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doErc20PaymentFromMainNet", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "ERC-20 PAYMENT FROM MAIN NET", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doErc20PaymentFromMainNet", true ); + details.close(); + return true; +} + +export async function doErc1155PaymentFromMainNet( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, + chainIdSChain: string, + joAccountSrc: state.TAccount, + joAccountDst: state.TAccount, + joDepositBoxERC1155: owaspUtils.ethersMod.ethers.Contract, + joMessageProxyMainNet: owaspUtils.ethersMod.ethers.Contract, // for checking logs + chainNameSChain: string, + tokenId: any, // which ERC1155 token id to send + tokenAmount: any, // which ERC1155 token id to send + weiHowMuch: any, // how much ETH + joTokenManagerERC1155: owaspUtils.ethersMod.ethers.Contract, // only s-chain + strCoinNameErc1155SMainNet: string, + erc1155PrivateTestnetJsonMainNet: any, + strCoinNameErc1155SChain: string, + erc1155PrivateTestnetJsonSChain: any, + transactionCustomizerMainNet: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "M2S ERC1155 Payment: "; + try { + strActionName = "ERC1155 payment from Main Net, approve"; + const erc1155ABI = + erc1155PrivateTestnetJsonMainNet[strCoinNameErc1155SMainNet + "_abi"]; + const erc1155AddressMainNet = + erc1155PrivateTestnetJsonMainNet[strCoinNameErc1155SMainNet + "_address"]; + const contractERC1155 = new owaspUtils.ethersMod.ethers.Contract( + erc1155AddressMainNet, erc1155ABI, ethersProviderMainNet ); + const depositBoxAddress = joDepositBoxERC1155.address; + const arrArgumentsApprove = [ + depositBoxAddress, + true + ]; + const arrArgumentsDepositERC1155 = [ + chainNameSChain, + erc1155AddressMainNet, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenId ).toHexString() ), + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenAmount ).toHexString() ) + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = + await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(approve) gas={}", strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = + await imaTx.dryRunCall( + details, ethersProviderMainNet, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const joReceiptApprove = + await imaTx.payedCall( + details, ethersProviderMainNet, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, + null ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: "doErc1155PaymentFromMainNet/approve", + receipt: joReceiptApprove + } ); + } + strActionName = "ERC1155 payment from Main Net, depositERC1155"; + const weiHowMuchDepositERC1155 = undefined; + gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasDeposit = + await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "DepositBoxERC1155", joDepositBoxERC1155, + "depositERC1155", arrArgumentsDepositERC1155, + joAccountSrc, strActionName, + gasPrice, 8000000, weiHowMuchDepositERC1155, null ); + details.trace( "{p}Using estimated(deposit) gas={}", strLogPrefix, estimatedGasDeposit ); + const isIgnoreDepositERC1155 = true; + const strErrorOfDryRunDepositERC1155 = + await imaTx.dryRunCall( + details, ethersProviderMainNet, + "DepositBoxERC1155", joDepositBoxERC1155, + "depositERC1155", arrArgumentsDepositERC1155, + joAccountSrc, strActionName, isIgnoreDepositERC1155, + gasPrice, estimatedGasDeposit, weiHowMuchDepositERC1155, null ); + if( strErrorOfDryRunDepositERC1155 ) + throw new Error( strErrorOfDryRunDepositERC1155 ); + const joReceiptDeposit = + await imaTx.payedCall( + details, ethersProviderMainNet, + "DepositBoxERC1155", joDepositBoxERC1155, + "depositERC1155", arrArgumentsDepositERC1155, + joAccountSrc, strActionName, + gasPrice, estimatedGasDeposit, weiHowMuchDepositERC1155, null ); + if( joReceiptDeposit ) { + jarrReceipts.push( { + description: "doErc1155PaymentFromMainNet/deposit", + receipt: joReceiptDeposit + } ); + } + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxyMainNet ) { + details.trace( "{p}Verifying the {} event of the MessageProxy/{} contract...", + strLogPrefix, strEventName, joMessageProxyMainNet.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderMainNet, joMessageProxyMainNet, strEventName, + joReceiptDeposit.blockNumber, joReceiptDeposit.transactionHash, + joMessageProxyMainNet.filters[strEventName]() ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{}" + + " contract, found event(s): ", strLogPrefix, strEventName, + joMessageProxyMainNet.address, joEvents ); + } else { + throw new Error( "Verification failed for theOutgoingMessage event of the " + + `MessageProxy ${joMessageProxyMainNet.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doErc1155PaymentFromMainNet", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "ERC-1155 PAYMENT FROM MAIN NET", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doErc1155PaymentFromMainNet", true ); + details.close(); + return true; +} + +export async function doErc1155BatchPaymentFromMainNet( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, chainIdSChain: string, + joAccountSrc: state.TAccount, joAccountDst: state.TAccount, + joDepositBoxERC1155: owaspUtils.ethersMod.ethers.Contract, + joMessageProxyMainNet: owaspUtils.ethersMod.ethers.Contract, // for checking logs + chainNameSChain: string, + arrTokenIds: any[], // which ERC1155 token id to send + arrTokenAmounts: any[], // which ERC1155 token id to send + weiHowMuch: any, // how much ETH + joTokenManagerERC1155: owaspUtils.ethersMod.ethers.Contract, // only s-chain + strCoinNameErc1155SMainNet: string, + erc1155PrivateTestnetJsonMainNet: any, strCoinNameErc1155SChain: string, + erc1155PrivateTestnetJsonSChain: any, + transactionCustomizerMainNet: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "M2S ERC1155 Batch Payment: "; + try { + strActionName = "ERC1155 batch-payment from Main Net, approve"; + const erc1155ABI = + erc1155PrivateTestnetJsonMainNet[strCoinNameErc1155SMainNet + "_abi"]; + const erc1155AddressMainNet = + erc1155PrivateTestnetJsonMainNet[strCoinNameErc1155SMainNet + "_address"]; + const contractERC1155 = new owaspUtils.ethersMod.ethers.Contract( + erc1155AddressMainNet, erc1155ABI, ethersProviderMainNet ); + const depositBoxAddress = joDepositBoxERC1155.address; + const arrArgumentsApprove = [ depositBoxAddress, true ]; + const arrArgumentsDepositERC1155Batch = [ + chainNameSChain, erc1155AddressMainNet, arrTokenIds, arrTokenAmounts ]; + const weiHowMuchApprove = undefined; + let gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(approve) gas={}", strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderMainNet, "ERC1155", contractERC1155, + "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, gasPrice, estimatedGasApprove, + weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderMainNet, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: "doErc1155BatchPaymentFromMainNet/approve", + receipt: joReceiptApprove + } ); + } + strActionName = "ERC1155 batch-payment from Main Net, depositERC1155Batch"; + const weiHowMuchDepositERC1155Batch = undefined; + gasPrice = await transactionCustomizerMainNet.computeGasPrice( + ethersProviderMainNet, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasDeposit = await transactionCustomizerMainNet.computeGas( + details, ethersProviderMainNet, + "DepositBoxERC1155", joDepositBoxERC1155, + "depositERC1155Batch", arrArgumentsDepositERC1155Batch, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchDepositERC1155Batch, null ); + details.trace( "{p}Using estimated(deposit) gas={}", strLogPrefix, estimatedGasDeposit ); + const isIgnoreDepositERC1155Batch = true; + const strErrorOfDryRunDepositERC1155Batch = await imaTx.dryRunCall( + details, ethersProviderMainNet, + "DepositBoxERC1155", joDepositBoxERC1155, + "depositERC1155Batch", arrArgumentsDepositERC1155Batch, + joAccountSrc, strActionName, isIgnoreDepositERC1155Batch, + gasPrice, estimatedGasDeposit, weiHowMuchDepositERC1155Batch, null ); + if( strErrorOfDryRunDepositERC1155Batch ) + throw new Error( strErrorOfDryRunDepositERC1155Batch ); + const joReceiptDeposit = await imaTx.payedCall( + details, ethersProviderMainNet, + "DepositBoxERC1155", joDepositBoxERC1155, + "depositERC1155Batch", arrArgumentsDepositERC1155Batch, + joAccountSrc, strActionName, + gasPrice, estimatedGasDeposit, weiHowMuchDepositERC1155Batch, null ); + if( joReceiptDeposit ) { + jarrReceipts.push( { + description: "doErc1155BatchPaymentFromMainNet/deposit", + receipt: joReceiptDeposit + } ); + } + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxyMainNet ) { + details.debug( "{p}Verifying the {} event of the MessageProxy/{} contract...", + strLogPrefix, strEventName, joMessageProxyMainNet.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderMainNet, joMessageProxyMainNet, strEventName, + joReceiptDeposit.blockNumber, joReceiptDeposit.transactionHash, + joMessageProxyMainNet.filters[strEventName]() ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{}" + + " contract, found event(s): {}", strLogPrefix, strEventName, + joMessageProxyMainNet.address, joEvents ); + } else { + throw new Error( "Verification failed for the OutgoingMessage event of the " + + `MessageProxy ${joMessageProxyMainNet.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doErc1155BatchPaymentFromMainNet", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "ERC-1155 PAYMENT FROM MAIN NET", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doErc1155BatchPaymentFromMainNet", true ); + details.close(); + return true; +} + +export async function doErc20PaymentFromSChain( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, + chainIdSChain: string, + joAccountSrc: state.TAccount, + joAccountDst: state.TAccount, + joTokenManagerERC20: owaspUtils.ethersMod.ethers.Contract, // only s-chain + joMessageProxySChain: owaspUtils.ethersMod.ethers.Contract, // for checking logs + joDepositBox: owaspUtils.ethersMod.ethers.Contract, // only main net + tokenAmount: any, // how much ERC20 tokens to send + weiHowMuch: any, // how much ETH + strCoinNameErc20MainNet: string, + joErc20MainNet: any, + strCoinNameErc20SChain: string, + joErc20SChain: any, + transactionCustomizerSChain: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "S2M ERC20 Payment: "; + try { + strActionName = "ERC20 payment from S-Chain, approve"; + const erc20ABI = joErc20SChain[strCoinNameErc20SChain + "_abi"]; + const erc20AddressSChain = joErc20SChain[strCoinNameErc20SChain + "_address"]; + const tokenManagerAddress = joTokenManagerERC20.address; + const contractERC20 = new owaspUtils.ethersMod.ethers.Contract( + erc20AddressSChain, erc20ABI, ethersProviderSChain ); + const arrArgumentsApprove = [ + tokenManagerAddress, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenAmount ).toHexString() ) ]; + const erc20AddressMainNet = joErc20MainNet[strCoinNameErc20MainNet + "_address"]; + const arrArgumentsExitToMainERC20 = [ + erc20AddressMainNet, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenAmount ).toHexString() ) + // owaspUtils.ensureStartsWith0x( owaspUtils.toBN( weiHowMuch ).toHexString() ) + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "ERC20", contractERC20, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(approve) gas={}", strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderSChain, + "ERC20", contractERC20, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, gasPrice, estimatedGasApprove, + weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const opts: imaTx.TCustomPayedCallOptions = { isCheckTransactionToSchain: true }; + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderSChain, + "ERC20", contractERC20, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, opts ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: "doErc20PaymentFromSChain/approve", + receipt: joReceiptApprove + } ); + } + const nSleep = imaHelperAPIs.getSleepBetweenTransactionsOnSChainMilliseconds(); + if( nSleep > 0 ) { + details.trace( "Sleeping {} milliseconds between transactions...", nSleep ); + await threadInfo.sleep( nSleep ); + } + if( imaHelperAPIs.getWaitForNextBlockOnSChain() ) + await imaHelperAPIs.safeWaitForNextBlockToAppear( details, ethersProviderSChain ); + strActionName = "ERC20 payment from S-Chain, exitToMainERC20"; + const weiHowMuchExitToMainERC20 = undefined; + const estimatedGasExitToMainERC20 = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "TokenManagerERC20", joTokenManagerERC20, + "exitToMainERC20", arrArgumentsExitToMainERC20, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchExitToMainERC20, null ); + details.trace( "{p}Using estimated(approve) gas={}", + strLogPrefix, estimatedGasExitToMainERC20 ); + gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const isIgnoreExitToMainERC20 = true; + const strErrorOfDryRunExitToMainERC20 = await imaTx.dryRunCall( + details, ethersProviderSChain, + "TokenManagerERC20", joTokenManagerERC20, + "exitToMainERC20", arrArgumentsExitToMainERC20, + joAccountSrc, strActionName, isIgnoreExitToMainERC20, + gasPrice, estimatedGasExitToMainERC20, weiHowMuchExitToMainERC20, null ); + if( strErrorOfDryRunExitToMainERC20 ) + throw new Error( strErrorOfDryRunExitToMainERC20 ); + opts.isCheckTransactionToSchain = true; + const joReceiptExitToMainERC20 = await imaTx.payedCall( + details, ethersProviderSChain, + "TokenManagerERC20", joTokenManagerERC20, + "exitToMainERC20", arrArgumentsExitToMainERC20, + joAccountSrc, strActionName, gasPrice, + estimatedGasExitToMainERC20, weiHowMuchExitToMainERC20, opts ); + if( joReceiptExitToMainERC20 ) { + jarrReceipts.push( { + description: "doErc20PaymentFromSChain/exit-to-main", + receipt: joReceiptExitToMainERC20 + } ); + } + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxySChain ) { + details.debug( "{p}Verifying the {} event of the MessageProxy/{} contract...", + strLogPrefix, strEventName, joMessageProxySChain.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderSChain, joMessageProxySChain, strEventName, + joReceiptExitToMainERC20.blockNumber, joReceiptExitToMainERC20.transactionHash, + joMessageProxySChain.filters[strEventName]() ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{}" + + " contract, found event(s): {}", strLogPrefix, strEventName, + joMessageProxySChain.address, joEvents ); + } else { + throw new Error( "Verification failed for the OutgoingMessage event of the " + + `MessageProxy ${joMessageProxySChain.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doErc20PaymentFromSChain", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "ERC-20 PAYMENT FROM S-CHAIN", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doErc20PaymentFromSChain", true ); + details.close(); + return true; +} + +export async function doErc721PaymentFromSChain( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, + chainIdSChain: string, + joAccountSrc: state.TAccount, + joAccountDst: state.TAccount, + joTokenManagerERC721: owaspUtils.ethersMod.ethers.Contract, // only s-chain + joMessageProxySChain: owaspUtils.ethersMod.ethers.Contract, // for checking logs + joDepositBox: owaspUtils.ethersMod.ethers.Contract, // only main net + tokenId: any, // which ERC721 token id to send + weiHowMuch: any, // how much ETH + strCoinNameErc721MainNet: string, + joErc721MainNet: any, + strCoinNameErc721SChain: string, + joErc721SChain: any, + transactionCustomizerSChain: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "S2M ERC721 Payment: "; + try { + strActionName = "ERC721 payment from S-Chain, approve"; + const erc721ABI = joErc721SChain[strCoinNameErc721SChain + "_abi"]; + const erc721AddressSChain = joErc721SChain[strCoinNameErc721SChain + "_address"]; + const tokenManagerAddress = joTokenManagerERC721.address; + const contractERC721 = new owaspUtils.ethersMod.ethers.Contract( + erc721AddressSChain, erc721ABI, ethersProviderSChain ); + const arrArgumentsApprove = [ + tokenManagerAddress, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenId ).toHexString() ) + ]; + const erc721AddressMainNet = + joErc721MainNet[strCoinNameErc721MainNet + "_address"]; + const arrArgumentsExitToMainERC721 = [ + erc721AddressMainNet, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenId ).toHexString() ) + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "ERC721", contractERC721, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(transfer from) gas={}", + strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderSChain, + "ERC721", contractERC721, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const opts: imaTx.TCustomPayedCallOptions = { isCheckTransactionToSchain: true }; + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderSChain, + "ERC721", contractERC721, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, opts ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: "doErc721PaymentFromSChain/transfer-from", + receipt: joReceiptApprove + } ); + } + const nSleep = imaHelperAPIs.getSleepBetweenTransactionsOnSChainMilliseconds(); + if( nSleep > 0 ) { + details.trace( "Sleeping {} milliseconds between transactions...", nSleep ); + await threadInfo.sleep( nSleep ); + } + if( imaHelperAPIs.getWaitForNextBlockOnSChain() ) + await imaHelperAPIs.safeWaitForNextBlockToAppear( details, ethersProviderSChain ); + strActionName = "ERC721 payment from S-Chain, exitToMainERC721"; + const weiHowMuchExitToMainERC721 = undefined; + gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasExitToMainERC721 = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "TokenManagerERC721", joTokenManagerERC721, + "exitToMainERC721", arrArgumentsExitToMainERC721, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchExitToMainERC721, null ); + details.trace( "{p}Using estimated(exit to main) gas={}", + strLogPrefix, estimatedGasExitToMainERC721 ); + const isIgnoreExitToMainERC721 = true; + const strErrorOfDryRunExitToMainERC721 = await imaTx.dryRunCall( + details, ethersProviderSChain, + "TokenManagerERC721", joTokenManagerERC721, + "exitToMainERC721", arrArgumentsExitToMainERC721, + joAccountSrc, strActionName, isIgnoreExitToMainERC721, gasPrice, + estimatedGasExitToMainERC721, weiHowMuchExitToMainERC721, null ); + if( strErrorOfDryRunExitToMainERC721 ) + throw new Error( strErrorOfDryRunExitToMainERC721 ); + opts.isCheckTransactionToSchain = true; + const joReceiptExitToMainERC721 = await imaTx.payedCall( + details, ethersProviderSChain, + "TokenManagerERC721", joTokenManagerERC721, + "exitToMainERC721", arrArgumentsExitToMainERC721, + joAccountSrc, strActionName, gasPrice, + estimatedGasExitToMainERC721, weiHowMuchExitToMainERC721, opts ); + if( joReceiptExitToMainERC721 ) { + jarrReceipts.push( { + description: "doErc721PaymentFromSChain/exit-to-main", + receipt: joReceiptExitToMainERC721 + } ); + } + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxySChain ) { + details.debug( "{p}Verifying the {} event of the MessageProxy/{} contract...", + strLogPrefix, strEventName, joMessageProxySChain.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderSChain, joMessageProxySChain, strEventName, + joReceiptExitToMainERC721.blockNumber, + joReceiptExitToMainERC721.transactionHash, + joMessageProxySChain.filters[strEventName]() ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{}" + + " contract, found event(s): {}", strLogPrefix, strEventName, + joMessageProxySChain.address, joEvents ); + } else { + throw new Error( "Verification failed for the OutgoingMessage event of the " + + `MessageProxy${joMessageProxySChain.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doErc721PaymentFromSChain", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "ERC-721 PAYMENT FROM S-CHAIN", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doErc721PaymentFromSChain", true ); + details.close(); + return true; +} + +export async function doErc1155PaymentFromSChain( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, + chainIdSChain: string, + joAccountSrc: state.TAccount, + joAccountDst: state.TAccount, + joTokenManagerERC1155: owaspUtils.ethersMod.ethers.Contract, // only s-chain + joMessageProxySChain: owaspUtils.ethersMod.ethers.Contract, // for checking logs + joDepositBox: owaspUtils.ethersMod.ethers.Contract, // only main net + tokenId: any, // which ERC1155 token id to send + tokenAmount: any, // which ERC1155 token id to send + weiHowMuch: any, // how much ETH + strCoinNameErc1155SMainNet: string, + joErc1155MainNet: any, + strCoinNameErc1155SChain: string, + joErc1155Chain: any, + transactionCustomizerSChain: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "S2M ERC1155 Payment: "; + try { + strActionName = "ERC1155 payment from S-Chain, approve"; + const erc1155ABI = joErc1155Chain[strCoinNameErc1155SChain + "_abi"]; + const erc1155AddressSChain = joErc1155Chain[strCoinNameErc1155SChain + "_address"]; + const tokenManagerAddress = joTokenManagerERC1155.address; + const contractERC1155 = new owaspUtils.ethersMod.ethers.Contract( + erc1155AddressSChain, erc1155ABI, ethersProviderSChain ); + const arrArgumentsApprove = [ tokenManagerAddress, true ]; + const erc1155AddressMainNet = + joErc1155MainNet[strCoinNameErc1155SMainNet + "_address"]; + const arrArgumentsExitToMainERC1155 = [ + erc1155AddressMainNet, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenId ).toHexString() ), + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenAmount ).toHexString() ) + // owaspUtils.ensureStartsWith0x( owaspUtils.toBN( weiHowMuch ).toHexString() ) + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(transfer from) gas={}", + strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderSChain, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const opts: imaTx.TCustomPayedCallOptions = { isCheckTransactionToSchain: true }; + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderSChain, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, opts ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: "doErc1155PaymentFromSChain/transfer-from", + receipt: joReceiptApprove + } ); + } + const nSleep = imaHelperAPIs.getSleepBetweenTransactionsOnSChainMilliseconds(); + if( nSleep > 0 ) { + details.trace( "Sleeping {} milliseconds between transactions...", nSleep ); + await threadInfo.sleep( nSleep ); + } + if( imaHelperAPIs.getWaitForNextBlockOnSChain() ) + await imaHelperAPIs.safeWaitForNextBlockToAppear( details, ethersProviderSChain ); + strActionName = "ERC1155 payment from S-Chain, exitToMainERC1155"; + const weiHowMuchExitToMainERC1155 = undefined; + gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasExitToMainERC1155 = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "TokenManagerERC1155", joTokenManagerERC1155, + "exitToMainERC1155", arrArgumentsExitToMainERC1155, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchExitToMainERC1155, null ); + details.trace( "{p}Using estimated(exit to main) gas={}", + strLogPrefix, estimatedGasExitToMainERC1155 ); + const isIgnoreExitToMainERC1155 = true; + const strErrorOfDryRunExitToMainERC1155 = await imaTx.dryRunCall( + details, ethersProviderSChain, + "TokenManagerERC1155", joTokenManagerERC1155, + "exitToMainERC1155", arrArgumentsExitToMainERC1155, + joAccountSrc, strActionName, isIgnoreExitToMainERC1155, + gasPrice, estimatedGasExitToMainERC1155, weiHowMuchExitToMainERC1155, null ); + if( strErrorOfDryRunExitToMainERC1155 ) + throw new Error( strErrorOfDryRunExitToMainERC1155 ); + opts.isCheckTransactionToSchain = true; + const joReceiptExitToMainERC1155 = await imaTx.payedCall( + details, ethersProviderSChain, + "TokenManagerERC1155", joTokenManagerERC1155, + "exitToMainERC1155", arrArgumentsExitToMainERC1155, + joAccountSrc, strActionName, gasPrice, + estimatedGasExitToMainERC1155, weiHowMuchExitToMainERC1155, opts ); + if( joReceiptExitToMainERC1155 ) { + jarrReceipts.push( { + description: "doErc1155PaymentFromSChain/exit-to-main", + receipt: joReceiptExitToMainERC1155 + } ); + } + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxySChain ) { + details.debug( "{p}Verifying the {} event of the MessageProxy/{} contract...", + strLogPrefix, strEventName, joMessageProxySChain.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderSChain, joMessageProxySChain, strEventName, + joReceiptExitToMainERC1155.blockNumber, + joReceiptExitToMainERC1155.transactionHash, + joMessageProxySChain.filters[strEventName]() ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{}" + + " contract, found event(s): {}", strLogPrefix, strEventName, + joMessageProxySChain.address, joEvents ); + } else { + throw new Error( "Verification failed for the OutgoingMessage event of the " + + `MessageProxy${joMessageProxySChain.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doErc1155PaymentFromSChain", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "ERC-1155 PAYMENT FROM S-CHAIN", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doErc1155PaymentFromSChain", true ); + details.close(); + return true; +} + +export async function doErc1155BatchPaymentFromSChain( + ethersProviderMainNet: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + ethersProviderSChain: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdMainNet: string, + chainIdSChain: string, + joAccountSrc: state.TAccount, + joAccountDst: state.TAccount, + joTokenManagerERC1155: owaspUtils.ethersMod.ethers.Contract, // only s-chain + joMessageProxySChain: owaspUtils.ethersMod.ethers.Contract, // for checking logs + joDepositBox: owaspUtils.ethersMod.ethers.Contract, // only main net + arrTokenIds: any[], // which ERC1155 token ids to send + arrTokenAmounts: any[], // which ERC1155 token amounts to send + weiHowMuch: any, // how much ETH + strCoinNameErc1155SMainNet: string, + joErc1155MainNet: any, + strCoinNameErc1155SChain: string, + joErc1155Chain: any, + transactionCustomizerSChain: imaTx.TransactionCustomizer +): Promise { + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = "S2M ERC1155 Batch Payment: "; + try { + strActionName = "ERC1155 payment from S-Chain, approve"; + const erc1155ABI = joErc1155Chain[strCoinNameErc1155SChain + "_abi"]; + const erc1155AddressSChain = joErc1155Chain[strCoinNameErc1155SChain + "_address"]; + const tokenManagerAddress = joTokenManagerERC1155.address; + const contractERC1155 = new owaspUtils.ethersMod.ethers.Contract( + erc1155AddressSChain, erc1155ABI, ethersProviderSChain ); + const arrArgumentsApprove = [ tokenManagerAddress, true ]; + const erc1155AddressMainNet = + joErc1155MainNet[strCoinNameErc1155SMainNet + "_address"]; + const arrArgumentsExitToMainERC1155Batch = [ + erc1155AddressMainNet, arrTokenIds, arrTokenAmounts ]; + const weiHowMuchApprove = undefined; + let gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(transfer from) gas={}", + strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderSChain, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const opts: imaTx.TCustomPayedCallOptions = { isCheckTransactionToSchain: true }; + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderSChain, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, opts ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: "doErc1155BatchPaymentFromSChain/transfer-from", + receipt: joReceiptApprove + } ); + } + const nSleep = imaHelperAPIs.getSleepBetweenTransactionsOnSChainMilliseconds(); + if( nSleep > 0 ) { + details.trace( "Sleeping {} milliseconds between transactions...", nSleep ); + await threadInfo.sleep( nSleep ); + } + if( imaHelperAPIs.getWaitForNextBlockOnSChain() ) + await imaHelperAPIs.safeWaitForNextBlockToAppear( details, ethersProviderSChain ); + strActionName = "ERC1155 batch-payment from S-Chain, exitToMainERC1155Batch"; + const weiHowMuchExitToMainERC1155Batch = undefined; + gasPrice = await transactionCustomizerSChain.computeGasPrice( + ethersProviderSChain, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasExitToMainERC1155Batch = await transactionCustomizerSChain.computeGas( + details, ethersProviderSChain, + "TokenManagerERC1155", joTokenManagerERC1155, + "exitToMainERC1155Batch", arrArgumentsExitToMainERC1155Batch, + joAccountSrc, strActionName, gasPrice, 8000000, + weiHowMuchExitToMainERC1155Batch, null ); + details.trace( "{p}Using estimated(exit to main) gas={}", + strLogPrefix, estimatedGasExitToMainERC1155Batch ); + const isIgnoreExitToMainERC1155Batch = true; + const strErrorOfDryRunExitToMainERC1155Batch = await imaTx.dryRunCall( + details, ethersProviderSChain, + "TokenManagerERC1155", joTokenManagerERC1155, + "exitToMainERC1155Batch", arrArgumentsExitToMainERC1155Batch, + joAccountSrc, strActionName, isIgnoreExitToMainERC1155Batch, gasPrice, + estimatedGasExitToMainERC1155Batch, weiHowMuchExitToMainERC1155Batch, null ); + if( strErrorOfDryRunExitToMainERC1155Batch ) + throw new Error( strErrorOfDryRunExitToMainERC1155Batch ); + opts.isCheckTransactionToSchain = true; + const joReceiptExitToMainERC1155Batch = await imaTx.payedCall( + details, ethersProviderSChain, + "TokenManagerERC1155", joTokenManagerERC1155, + "exitToMainERC1155Batch", arrArgumentsExitToMainERC1155Batch, + joAccountSrc, strActionName, gasPrice, + estimatedGasExitToMainERC1155Batch, weiHowMuchExitToMainERC1155Batch, opts ); + if( joReceiptExitToMainERC1155Batch ) { + jarrReceipts.push( { + description: "doErc1155BatchPaymentFromSChain/exit-to-main", + receipt: joReceiptExitToMainERC1155Batch + } ); + } + // Must-have event(s) analysis as indicator(s) of success + const strEventName = "OutgoingMessage"; + if( joMessageProxySChain ) { + details.debug( "{p}Verifying the {} event of the MessageProxy/{} contract...", + strLogPrefix, strEventName, joMessageProxySChain.address ); + await threadInfo.sleep( + imaHelperAPIs.getMillisecondsSleepBeforeFetchOutgoingMessageEvent() ); + const joEvents = await imaEventLogScan.getContractCallEvents( + details, strLogPrefix, + ethersProviderSChain, joMessageProxySChain, strEventName, + joReceiptExitToMainERC1155Batch.blockNumber, + joReceiptExitToMainERC1155Batch.transactionHash, + joMessageProxySChain.filters[strEventName]() ); + if( joEvents.length > 0 ) { + details.success( + "{p}Success, verified the {} event of the MessageProxy/{}" + + " contract, found event(s): {}", strLogPrefix, strEventName, + joMessageProxySChain.address, joEvents ); + } else { + throw new Error( "Verification failed for the OutgoingMessage event of the " + + `MessageProxy${joMessageProxySChain.address} contract, no events found` ); + } + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "doErc1155BatchPaymentFromSChain", false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + "ERC-1155 PAYMENT FROM S-CHAIN", jarrReceipts, details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "doErc1155BatchPaymentFromSChain", true ); + details.close(); + return true; +} + +export async function doErc20PaymentS2S( + isForward: boolean, + ethersProviderSrc: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdSrc: string, + strChainNameDst: string, + joAccountSrc: state.TAccount, + joTokenManagerERC20Src: owaspUtils.ethersMod.ethers.Contract, + nAmountOfToken: any, // how much ERC20 tokens to send + nAmountOfWei: any, // how much to send + strCoinNameErc20Src: string, + joSrcErc20: any, + ercDstAddress20: any, // only reverse payment needs it + tc: imaTx.TransactionCustomizer +): Promise { + const isReverse = !isForward; + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = `S2S ERC20 Payment(${( isForward ? "forward" : "reverse" )}:): `; + try { + strActionName = `validateArgs/doErc20PaymentS2S/${( isForward ? "forward" : "reverse" )}`; + if( !ethersProviderSrc ) + throw new Error( "No ethers provider specified for source of transfer" ); + if( !strChainNameDst ) + throw new Error( "No destination chain name provided" ); + if( !joAccountSrc ) + throw new Error( "No account or sign TX way provided" ); + if( !strCoinNameErc20Src ) + throw new Error( "Need full source ERC20 information, like ABI" ); + if( !joSrcErc20 ) + throw new Error( "No source ERC20 ABI provided" ); + if( isReverse ) { + if( !ercDstAddress20 ) + throw new Error( "No destination ERC20 address provided" ); + } + if( !tc ) + throw new Error( "No transaction customizer provided" ); + const ercSrcAbi20 = joSrcErc20[strCoinNameErc20Src + "_abi"]; + const ercSrcAddress20 = joSrcErc20[strCoinNameErc20Src + "_address"]; + details.trace( "{p}Token Manager ERC20 address on source chain....{}", + strLogPrefix, joTokenManagerERC20Src.address ); + details.trace( "{p}Source ERC20 coin name.........................{}", + strLogPrefix, strCoinNameErc20Src ); + details.trace( "{p}Source ERC20 token address.....................{}", + strLogPrefix, ercSrcAddress20 ); + if( isReverse || ercDstAddress20 ) { + details.trace( "{p}Destination ERC20 token address................{}", + strLogPrefix, ercDstAddress20 ); + } + details.trace( "{p}Destination chain name.........................{}", + strLogPrefix, strChainNameDst ); + details.trace( "{p}Amount of tokens to transfer...................{}", + strLogPrefix, nAmountOfToken ); + strActionName = `ERC20 payment S2S, approve, ${( isForward ? "forward" : "reverse" )}`; + const contractERC20 = new owaspUtils.ethersMod.ethers.Contract( + ercSrcAddress20, ercSrcAbi20, ethersProviderSrc ); + const arrArgumentsApprove = [ + joTokenManagerERC20Src.address, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( nAmountOfToken ).toHexString() ) + ]; + const arrArgumentsTransfer = [ + strChainNameDst, + isReverse ? ercDstAddress20 : ercSrcAddress20, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( nAmountOfToken ).toHexString() ) + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await tc.computeGasPrice( ethersProviderSrc, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await tc.computeGas( + details, ethersProviderSrc, + "ERC20", contractERC20, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(approve) gas={}", strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderSrc, + "ERC20", contractERC20, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderSrc, + "ERC20", contractERC20, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: `doErc20PaymentS2S/approve/${( isForward ? "forward" : "reverse" )}`, + receipt: joReceiptApprove + } ); + } + strActionName = `ERC20 payment S2S, transferERC20 ${( isForward ? "forward" : "reverse" )}`; + const weiHowMuchTransferERC20 = undefined; + gasPrice = await tc.computeGasPrice( ethersProviderSrc, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasTransfer = await tc.computeGas( + details, ethersProviderSrc, + "TokenManagerERC20", joTokenManagerERC20Src, + "transferToSchainERC20", arrArgumentsTransfer, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchTransferERC20, null ); + details.trace( "{p}Using estimated(transfer) gas={}", strLogPrefix, estimatedGasTransfer ); + const isIgnoreTransferERC20 = true; + const strErrorOfDryRunTransferERC20 = await imaTx.dryRunCall( + details, ethersProviderSrc, + "TokenManagerERC20", joTokenManagerERC20Src, + "transferToSchainERC20", arrArgumentsTransfer, + joAccountSrc, strActionName, isIgnoreTransferERC20, + gasPrice, estimatedGasTransfer, weiHowMuchTransferERC20, null ); + if( strErrorOfDryRunTransferERC20 ) + throw new Error( strErrorOfDryRunTransferERC20 ); + const joReceiptTransfer = await imaTx.payedCall( + details, ethersProviderSrc, + "TokenManagerERC20", joTokenManagerERC20Src, + "transferToSchainERC20", arrArgumentsTransfer, + joAccountSrc, strActionName, gasPrice, + estimatedGasTransfer, weiHowMuchTransferERC20, null ); + if( joReceiptTransfer ) { + jarrReceipts.push( { + description: "doErc20PaymentS2S/transfer", + receipt: joReceiptTransfer + } ); + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), + `doErc20PaymentS2S/${( isForward ? "forward" : "reverse" )}`, false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + `ERC-20 PAYMENT FROM S2S/${( isForward ? "forward" : "reverse" )}`, jarrReceipts, details ); + if( log.exposeDetailsGet() ) { + details.exposeDetailsTo( log.globalStream(), + `doErc20PaymentS2S/${( isForward ? "forward" : "reverse" )}`, true ); + } + details.close(); + return true; +} + +export async function doErc721PaymentS2S( + isForward: boolean, + ethersProviderSrc: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdSrc: string, + strChainNameDst: string, + joAccountSrc: state.TAccount, + joTokenManagerERC721Src: owaspUtils.ethersMod.ethers.Contract, + tokenId: any, // which ERC721 token id to send + nAmountOfWei: any, // how much to send + strCoinNameErc721Src: string, + joSrcErc721: any, + ercDstAddress721: any, // only reverse payment needs it + tc: imaTx.TransactionCustomizer +): Promise { + const isReverse = !isForward; + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = `S2S ERC721 Payment(${( isForward ? "forward" : "reverse" )}: `; + try { + strActionName = `validateArgs/doErc721PaymentS2S/${( isForward ? "forward" : "reverse" )}`; + if( !ethersProviderSrc ) + throw new Error( "No provider for source of transfer" ); + if( !strChainNameDst ) + throw new Error( "No destination chain name provided" ); + if( !joAccountSrc ) + throw new Error( "No account or sign TX way provided" ); + if( !strCoinNameErc721Src ) + throw new Error( "Need full source ERC721 information, like ABI" ); + if( !joSrcErc721 ) + throw new Error( "No source ERC721 ABI provided" ); + if( isReverse ) { + if( !ercDstAddress721 ) + throw new Error( "No destination ERC721 address provided" ); + } + if( !tc ) + throw new Error( "No transaction customizer provided" ); + const ercSrcAbi721 = joSrcErc721[strCoinNameErc721Src + "_abi"]; + const ercSrcAddress721 = joSrcErc721[strCoinNameErc721Src + "_address"]; + details.trace( "{p}Token Manager ERC721 address on source chain....{}", + strLogPrefix, joTokenManagerERC721Src.address ); + details.trace( "{p}Source ERC721 coin name.........................{}", + strLogPrefix, strCoinNameErc721Src ); + details.trace( "{p}Source ERC721 token address.....................{}", + strLogPrefix, ercSrcAddress721 ); + if( isReverse || ercDstAddress721 ) { + details.trace( "{p}Destination ERC721 token address................{}", + strLogPrefix, ercDstAddress721 ); + } + details.trace( "{p}Destination chain name.........................{}", + strLogPrefix, strChainNameDst ); + details.trace( "{p}Token ID to transfer...........................{}", + strLogPrefix, tokenId ); + strActionName = `ERC721 payment S2S, approve, ${( isForward ? "forward" : "reverse" )}`; + const contractERC721 = new owaspUtils.ethersMod.ethers.Contract( + ercSrcAddress721, ercSrcAbi721, ethersProviderSrc ); + const arrArgumentsApprove = [ + joTokenManagerERC721Src.address, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenId ).toHexString() ) + ]; + const arrArgumentsTransfer = [ + strChainNameDst, + isReverse ? ercDstAddress721 : ercSrcAddress721, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenId ).toHexString() ) + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await tc.computeGasPrice( ethersProviderSrc, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await tc.computeGas( + details, ethersProviderSrc, + "ERC721", contractERC721, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(approve) gas={}", strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderSrc, + "ERC721", contractERC721, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderSrc, + "ERC721", contractERC721, "approve", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: + `doErc721PaymentS2S/approve/${( isForward ? "forward" : "reverse" )}`, + receipt: joReceiptApprove + } ); + } + const isIgnoreTransferERC721 = true; + strActionName = + `ERC721 payment S2S, transferERC721 ${( isForward ? "forward" : "reverse" )}`; + const weiHowMuchTransferERC721 = undefined; + gasPrice = await tc.computeGasPrice( ethersProviderSrc, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasTransfer = await tc.computeGas( + details, ethersProviderSrc, + "TokenManagerERC721", joTokenManagerERC721Src, + "transferToSchainERC721", arrArgumentsTransfer, + joAccountSrc, strActionName, + gasPrice, 8000000, weiHowMuchTransferERC721, null ); + details.trace( "{p}Using estimated(transfer) gas={}", strLogPrefix, estimatedGasTransfer ); + const strErrorOfDryRunTransferERC721 = await imaTx.dryRunCall( + details, ethersProviderSrc, + "TokenManagerERC721", joTokenManagerERC721Src, + "transferToSchainERC721", arrArgumentsTransfer, + joAccountSrc, strActionName, isIgnoreTransferERC721, + gasPrice, estimatedGasTransfer, weiHowMuchTransferERC721, null ); + if( strErrorOfDryRunTransferERC721 ) + throw new Error( strErrorOfDryRunTransferERC721 ); + const joReceiptTransfer = await imaTx.payedCall( + details, ethersProviderSrc, + "TokenManagerERC721", joTokenManagerERC721Src, + "transferToSchainERC721", arrArgumentsTransfer, + joAccountSrc, strActionName, + gasPrice, estimatedGasTransfer, weiHowMuchTransferERC721, null ); + if( joReceiptTransfer ) { + jarrReceipts.push( { + description: "doErc721PaymentS2S/transfer", + receipt: joReceiptTransfer + } ); + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), + `doErc721PaymentS2S/${( isForward ? "forward" : "reverse" )}`, false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + `ERC-721 PAYMENT FROM S2S/${( isForward ? "forward" : "reverse" )}`, + jarrReceipts, details ); + if( log.exposeDetailsGet() ) { + details.exposeDetailsTo( log.globalStream(), + `doErc721PaymentS2S/${( isForward ? "forward" : "reverse" )}`, true ); + } + details.close(); + return true; +} + +export async function doErc1155PaymentS2S( + isForward: boolean, + ethersProviderSrc: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdSrc: string, + strChainNameDst: string, + joAccountSrc: state.TAccount, + joTokenManagerERC1155Src: owaspUtils.ethersMod.ethers.Contract, + tokenId: any, // which ERC721 token id to send + nAmountOfToken: any, // how much ERC1155 tokens to send + nAmountOfWei: any, // how much to send + strCoinNameErc1155Src: string, + joSrcErc1155: any, + ercDstAddress1155: any, // only reverse payment needs it + tc: imaTx.TransactionCustomizer +): Promise { + const isReverse = !isForward; + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = `S2S ERC1155 Payment(${( isForward ? "forward" : "reverse" )}): `; + try { + strActionName = `validateArgs/doErc1155PaymentS2S/${( isForward ? "forward" : "reverse" )}`; + if( !ethersProviderSrc ) + throw new Error( "No provider for source of transfer" ); + if( !strChainNameDst ) + throw new Error( "No destination chain name provided" ); + if( !joAccountSrc ) + throw new Error( "No account or sign TX way provided" ); + if( !strCoinNameErc1155Src ) + throw new Error( "Need full source ERC1155 information, like ABI" ); + if( !joSrcErc1155 ) + throw new Error( "No source ERC1155 ABI provided" ); + if( isReverse ) { + if( !ercDstAddress1155 ) + throw new Error( "No destination ERC1155 address provided" ); + } + if( !tc ) + throw new Error( "No transaction customizer provided" ); + const ercSrcAbi1155 = joSrcErc1155[strCoinNameErc1155Src + "_abi"]; + const ercSrcAddress1155 = joSrcErc1155[strCoinNameErc1155Src + "_address"]; + details.trace( "{p}Token Manager ERC1155 address on source chain....{}", + strLogPrefix, joTokenManagerERC1155Src.address ); + details.trace( "{p}Source ERC1155 coin name.........................{}", + strLogPrefix, strCoinNameErc1155Src ); + details.trace( "{p}Source ERC1155 token address.....................{}", + strLogPrefix, ercSrcAddress1155 ); + if( isReverse || ercDstAddress1155 ) { + details.trace( "{p}Destination ERC1155 token address................{}", + strLogPrefix, ercDstAddress1155 ); + } + details.trace( "{p}Destination chain name.........................{}", + strLogPrefix, strChainNameDst ); + details.trace( "{p}Token ID to transfer...........................{}", + strLogPrefix, tokenId ); + details.trace( "{p}Amount of tokens to transfer...................{}", + strLogPrefix, nAmountOfToken ); + strActionName = `ERC1155 payment S2S, approve, ${( isForward ? "forward" : "reverse" )}`; + const contractERC1155 = new owaspUtils.ethersMod.ethers.Contract( + ercSrcAddress1155, ercSrcAbi1155, ethersProviderSrc ); + const arrArgumentsApprove = [ joTokenManagerERC1155Src.address, true ]; + const arrArgumentsTransfer = [ + strChainNameDst, + isReverse ? ercDstAddress1155 : ercSrcAddress1155, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( tokenId ).toHexString() ), + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( nAmountOfToken ).toHexString() ) + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await tc.computeGasPrice( ethersProviderSrc, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await tc.computeGas( + details, ethersProviderSrc, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(approve) gas={}", strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderSrc, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderSrc, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, + estimatedGasApprove, weiHowMuchApprove, null ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: + `doErc1155PaymentS2S/approve/${( isForward ? "forward" : "reverse" )}`, + receipt: joReceiptApprove + } ); + } + strActionName = + `ERC1155 payment S2S, transferERC1155 ${( isForward ? "forward" : "reverse" )}`; + const weiHowMuchTransferERC1155 = undefined; + gasPrice = await tc.computeGasPrice( ethersProviderSrc, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasTransfer = await tc.computeGas( + details, ethersProviderSrc, + "TokenManagerERC1155", joTokenManagerERC1155Src, + "transferToSchainERC1155", arrArgumentsTransfer, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchTransferERC1155, null ); + details.trace( "{p}Using estimated(transfer) gas={}", strLogPrefix, estimatedGasTransfer ); + const isIgnoreTransferERC1155 = true; + const strErrorOfDryRunTransferERC1155 = await imaTx.dryRunCall( + details, ethersProviderSrc, + "TokenManagerERC1155", joTokenManagerERC1155Src, + "transferToSchainERC1155", arrArgumentsTransfer, + joAccountSrc, strActionName, isIgnoreTransferERC1155, gasPrice, + estimatedGasTransfer, weiHowMuchTransferERC1155, null ); + if( strErrorOfDryRunTransferERC1155 ) + throw new Error( strErrorOfDryRunTransferERC1155 ); + const joReceiptTransfer = await imaTx.payedCall( + details, ethersProviderSrc, + "TokenManagerERC1155", joTokenManagerERC1155Src, + "transferToSchainERC1155", arrArgumentsTransfer, + joAccountSrc, strActionName, gasPrice, estimatedGasTransfer, + weiHowMuchTransferERC1155, null ); + if( joReceiptTransfer ) { + jarrReceipts.push( { + description: "doErc1155PaymentS2S/transfer", + receipt: joReceiptTransfer + } ); + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), + `doErc1155PaymentS2S/${( isForward ? "forward" : "reverse" )}`, false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + `ERC-1155 PAYMENT FROM S2S/${( isForward ? "forward" : "reverse" )}`, + jarrReceipts, details ); + if( log.exposeDetailsGet() ) { + details.exposeDetailsTo( log.globalStream(), + `doErc1155PaymentS2S/${( isForward ? "forward" : "reverse" )}`, true ); + } + details.close(); + return true; +} + +export async function doErc1155BatchPaymentS2S( + isForward: boolean, + ethersProviderSrc: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainIdSrc: string, + strChainNameDst: string, + joAccountSrc: state.TAccount, + joTokenManagerERC1155Src: owaspUtils.ethersMod.ethers.Contract, + arrTokenIds: any[], // which ERC1155 token id to send + arrTokenAmounts: any[], // which ERC1155 token id to send + nAmountOfWei: any, // how much to send + strCoinNameErc1155Src: string, + joSrcErc1155: any, + ercDstAddress1155: any, // only reverse payment needs it + tc: imaTx.TransactionCustomizer +): Promise { + const isReverse = !isForward; + const details = log.createMemoryStream(); + const jarrReceipts: any = []; + let strActionName = ""; + const strLogPrefix = `S2S Batch ERC1155 Payment(${( isForward ? "forward" : "reverse" )}: `; + try { + strActionName = + `validateArgs/doErc1155BatchPaymentS2S/${( isForward ? "forward" : "reverse" )}`; + if( !ethersProviderSrc ) + throw new Error( "No provider for source of transfer" ); + if( !strChainNameDst ) + throw new Error( "No destination chain name provided" ); + if( !joAccountSrc ) + throw new Error( "No account or sign TX way provided" ); + if( !strCoinNameErc1155Src ) + throw new Error( "Need full source ERC1155 information, like ABI" ); + if( !joSrcErc1155 ) + throw new Error( "No source ERC1155 ABI provided" ); + if( isReverse ) { + if( !ercDstAddress1155 ) + throw new Error( "No destination ERC1155 address provided" ); + } + if( !tc ) + throw new Error( "No transaction customizer provided" ); + const ercSrcAbi1155 = joSrcErc1155[strCoinNameErc1155Src + "_abi"]; + const ercSrcAddress1155 = joSrcErc1155[strCoinNameErc1155Src + "_address"]; + details.trace( "{p}Token Manager ERC1155 address on source chain....{}", + strLogPrefix, joTokenManagerERC1155Src.address ); + details.trace( "{p}Source ERC1155 coin name.........................{}", + strLogPrefix, strCoinNameErc1155Src ); + details.trace( "{p}Source ERC1155 token address.....................{}", + strLogPrefix, ercSrcAddress1155 ); + if( isReverse || ercDstAddress1155 ) { + details.trace( "{p}Destination ERC1155 token address................{}", + strLogPrefix, ercDstAddress1155 ); + } + details.trace( "{p}Destination chain name.........................{}", + strLogPrefix, strChainNameDst ); + details.trace( "{p}Token IDs to transfer..........................{}", + strLogPrefix, arrTokenIds ); + details.trace( "{p}Amounts of tokens to transfer..................{}", + strLogPrefix, arrTokenAmounts ); + strActionName = + `ERC1155 batch-payment S2S, approve, ${( isForward ? "forward" : "reverse" )}`; + const contractERC1155 = new owaspUtils.ethersMod.ethers.Contract( + ercSrcAddress1155, ercSrcAbi1155, ethersProviderSrc ); + const arrArgumentsApprove = [ joTokenManagerERC1155Src.address, true ]; + const arrArgumentsTransfer = [ + strChainNameDst, + isReverse ? ercDstAddress1155 : ercSrcAddress1155, + arrTokenIds, + arrTokenAmounts + ]; + const weiHowMuchApprove = undefined; + let gasPrice = await tc.computeGasPrice( ethersProviderSrc, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasApprove = await tc.computeGas( + details, ethersProviderSrc, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, 8000000, weiHowMuchApprove, null ); + details.trace( "{p}Using estimated(approve) gas={}", strLogPrefix, estimatedGasApprove ); + const isIgnoreApprove = false; + const strErrorOfDryRunApprove = await imaTx.dryRunCall( + details, ethersProviderSrc, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, isIgnoreApprove, + gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( strErrorOfDryRunApprove ) + throw new Error( strErrorOfDryRunApprove ); + const joReceiptApprove = await imaTx.payedCall( + details, ethersProviderSrc, + "ERC1155", contractERC1155, "setApprovalForAll", arrArgumentsApprove, + joAccountSrc, strActionName, gasPrice, estimatedGasApprove, weiHowMuchApprove, null ); + if( joReceiptApprove ) { + jarrReceipts.push( { + description: + `doErc1155BatchPaymentS2S/approve/${( isForward ? "forward" : "reverse" )}`, + receipt: joReceiptApprove + } ); + } + strActionName = + `ERC1155 batch-payment S2S, transferERC1155 ${( isForward ? "forward" : "reverse" )}`; + const weiHowMuchTransferERC1155 = undefined; + gasPrice = await tc.computeGasPrice( ethersProviderSrc, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasTransfer = await tc.computeGas( + details, ethersProviderSrc, + "TokenManagerERC1155", joTokenManagerERC1155Src, + "transferToSchainERC1155Batch", arrArgumentsTransfer, + joAccountSrc, strActionName, + gasPrice, 8000000, weiHowMuchTransferERC1155, null ); + details.trace( "{p}Using estimated(transfer) gas={}", strLogPrefix, estimatedGasTransfer ); + const isIgnoreTransferERC1155 = true; + const strErrorOfDryRunTransferERC1155 = await imaTx.dryRunCall( + details, ethersProviderSrc, + "TokenManagerERC1155", joTokenManagerERC1155Src, + "transferToSchainERC1155Batch", arrArgumentsTransfer, + joAccountSrc, strActionName, isIgnoreTransferERC1155, + gasPrice, estimatedGasTransfer, weiHowMuchTransferERC1155, null ); + if( strErrorOfDryRunTransferERC1155 ) + throw new Error( strErrorOfDryRunTransferERC1155 ); + const joReceiptTransfer = await imaTx.payedCall( + details, ethersProviderSrc, + "TokenManagerERC1155", joTokenManagerERC1155Src, + "transferToSchainERC1155Batch", arrArgumentsTransfer, + joAccountSrc, strActionName, + gasPrice, estimatedGasTransfer, weiHowMuchTransferERC1155, null ); + if( joReceiptTransfer ) { + jarrReceipts.push( { + description: "doErc1155PaymentS2S/transfer", + receipt: joReceiptTransfer + } ); + } + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), + `doErc1155BatchPaymentS2S/${( isForward ? "forward" : "reverse" )}`, false ); + details.close(); + return false; + } + imaGasUsage.printGasUsageReportFromArray( + `ERC-1155-batch PAYMENT FROM S2S/${( isForward ? "forward" : "reverse" )}`, + jarrReceipts, details ); + if( log.exposeDetailsGet() ) { + details.exposeDetailsTo( log.globalStream(), + `doErc1155BatchPaymentS2S/${( isForward ? "forward" : "reverse" )}`, true ); + } + details.close(); + return true; +} + +export async function mintErc20( + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainId: string, + chainName: string, + joAccount: state.TAccount, + strAddressMintTo: string, + nAmount: any, + strTokenContractAddress: string, + joTokenContractABI: any, + tc: imaTx.TransactionCustomizer +): Promise { + let strActionName = "mintErc20() init"; + const strLogPrefix = "mintErc20() call "; + const details = log.createMemoryStream(); + try { + details.debug( "{p}Mint ERC20 token amount {}", strLogPrefix, nAmount ); + if( !( strAddressMintTo.length > 0 && strTokenContractAddress.length > 0 && + joTokenContractABI ) ) + throw new Error( "Missing valid arguments" ); + strActionName = "mintErc20() instantiate token contract"; + const contract = new owaspUtils.ethersMod.ethers.Contract( + strTokenContractAddress, joTokenContractABI, ethersProvider ); + const arrArgumentsMint = [ + strAddressMintTo, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( nAmount ).toHexString() ) + ]; + const weiHowMuchMint = undefined; + const gasPrice = await tc.computeGasPrice( ethersProvider, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasMint = await tc.computeGas( + details, ethersProvider, + "ERC20", contract, "mint", arrArgumentsMint, + joAccount, strActionName, gasPrice, 10000000, weiHowMuchMint, null ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGasMint ); + strActionName = "Mint ERC20"; + const isIgnoreMint = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProvider, + "ERC20", contract, "mint", arrArgumentsMint, + joAccount, strActionName, isIgnoreMint, + gasPrice, estimatedGasMint, weiHowMuchMint, null ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const opts: imaTx.TCustomPayedCallOptions = + { isCheckTransactionToSchain: ( chainName !== "Mainnet" ) }; + const joReceipt = await imaTx.payedCall( + details, ethersProvider, + "ERC20", contract, "mint", arrArgumentsMint, + joAccount, strActionName, gasPrice, estimatedGasMint, weiHowMuchMint, opts ); + imaGasUsage.printGasUsageReportFromArray( "MINT ERC20 ", [ { + description: "mintErc20()/mint", + receipt: joReceipt + } ], details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "mintErc20", true ); + details.close(); + return joReceipt; // can be used as "true" boolean value + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "mintErc20()", false ); + details.close(); + return false; + } +} + +export async function mintErc721( + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainId: string, + chainName: string, + joAccount: state.TAccount, + strAddressMintTo: string, + idToken: any, + strTokenContractAddress: string, + joTokenContractABI: any, + tc: imaTx.TransactionCustomizer +): Promise { + let strActionName = "mintErc721() init"; + const strLogPrefix = "mintErc721() call "; + const details = log.createMemoryStream(); + try { + details.debug( "{p}Mint ERC721 token ID {}", strLogPrefix, idToken ); + if( !( ethersProvider && joAccount && strAddressMintTo && + typeof strAddressMintTo === "string" && strAddressMintTo.length > 0 && + strTokenContractAddress && typeof strTokenContractAddress === "string" && + strTokenContractAddress.length > 0 && joTokenContractABI + ) ) + throw new Error( "Missing valid arguments" ); + strActionName = "mintErc721() instantiate token contract"; + const contract = new owaspUtils.ethersMod.ethers.Contract( + strTokenContractAddress, joTokenContractABI, ethersProvider ); + const arrArgumentsMint = [ + strAddressMintTo, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( idToken ).toHexString() ) + ]; + const weiHowMuchMint = undefined; + const gasPrice = await tc.computeGasPrice( ethersProvider, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasMint = await tc.computeGas( + details, ethersProvider, + "ERC721", contract, "mint", arrArgumentsMint, + joAccount, strActionName, gasPrice, 10000000, weiHowMuchMint, null ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGasMint ); + strActionName = "Mint ERC721"; + const isIgnoreMint = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProvider, + "ERC721", contract, "mint", arrArgumentsMint, + joAccount, strActionName, isIgnoreMint, + gasPrice, estimatedGasMint, weiHowMuchMint, null ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const opts: imaTx.TCustomPayedCallOptions = + { isCheckTransactionToSchain: ( chainName !== "Mainnet" ) }; + const joReceipt = await imaTx.payedCall( + details, ethersProvider, + "ERC721", contract, "mint", arrArgumentsMint, + joAccount, strActionName, gasPrice, estimatedGasMint, weiHowMuchMint, opts ); + imaGasUsage.printGasUsageReportFromArray( "MINT ERC721 ", [ { + description: "mintErc721()/mint", + receipt: joReceipt + } ], details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "mintErc721", true ); + details.close(); + return joReceipt; // can be used as "true" boolean value + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "mintErc721()", false ); + details.close(); + return false; + } +} + +export async function mintErc1155( + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainId: string, + chainName: any, + joAccount: state.TAccount, + strAddressMintTo: string, + idToken: any, + nAmount: any, + strTokenContractAddress: string, + joTokenContractABI: any, + tc: imaTx.TransactionCustomizer +): Promise { + let strActionName = "mintErc1155() init"; + const strLogPrefix = "mintErc1155() call "; + const details = log.createMemoryStream(); + try { + details.debug( "{p}Mint ERC1155 token ID {} token amount {}", + strLogPrefix, idToken, nAmount ); + if( !( ethersProvider && joAccount && strAddressMintTo && + typeof strAddressMintTo === "string" && strAddressMintTo.length > 0 && + strTokenContractAddress && typeof strTokenContractAddress === "string" && + strTokenContractAddress.length > 0 && joTokenContractABI + ) ) + throw new Error( "Missing valid arguments" ); + strActionName = "mintErc1155() instantiate token contract"; + const contract = new owaspUtils.ethersMod.ethers.Contract( + strTokenContractAddress, joTokenContractABI, ethersProvider ); + const arrArgumentsMint = [ + strAddressMintTo, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( idToken ).toHexString() ), + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( nAmount ).toHexString() ), + [] // data + ]; + const weiHowMuchMint = undefined; + const gasPrice = await tc.computeGasPrice( ethersProvider, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasMint = await tc.computeGas( + details, ethersProvider, + "ERC1155", contract, "mint", arrArgumentsMint, + joAccount, strActionName, gasPrice, 10000000, weiHowMuchMint, null ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGasMint ); + strActionName = "Mint ERC1155"; + const isIgnoreMint = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProvider, + "ERC1155", contract, "mint", arrArgumentsMint, + joAccount, strActionName, isIgnoreMint, + gasPrice, estimatedGasMint, weiHowMuchMint, null ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const opts: imaTx.TCustomPayedCallOptions = + { isCheckTransactionToSchain: ( chainName !== "Mainnet" ) }; + const joReceipt = await imaTx.payedCall( + details, ethersProvider, + "ERC1155", contract, "mint", arrArgumentsMint, + joAccount, strActionName, gasPrice, estimatedGasMint, weiHowMuchMint, opts ); + imaGasUsage.printGasUsageReportFromArray( "MINT ERC1155 ", [ { + description: "mintErc1155()/mint", + receipt: joReceipt + } ], details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "mintErc1155", true ); + details.close(); + return joReceipt; // can be used as "true" boolean value + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "mintErc1155()", false ); + details.close(); + return false; + } +} + +export async function burnErc20( + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainId: string, + chainName: string, + joAccount: state.TAccount, + strAddressBurnFrom: string, + nAmount: any, + strTokenContractAddress: string, + joTokenContractABI: any, + tc: imaTx.TransactionCustomizer +): Promise { + let strActionName = "burnErc20() init"; + const strLogPrefix = "burnErc20() call "; + const details = log.createMemoryStream(); + try { + details.debug( "{p}Burn ERC20 token amount {}", strLogPrefix, nAmount ); + if( !( ethersProvider && joAccount && strAddressBurnFrom && + typeof strAddressBurnFrom === "string" && strAddressBurnFrom.length > 0 && + strTokenContractAddress && typeof strTokenContractAddress === "string" && + strTokenContractAddress.length > 0 && joTokenContractABI + ) ) + throw new Error( "Missing valid arguments" ); + strActionName = "burnErc20() instantiate token contract"; + const contract = new owaspUtils.ethersMod.ethers.Contract( + strTokenContractAddress, joTokenContractABI, ethersProvider ); + const arrArgumentsBurn = [ + strAddressBurnFrom, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( nAmount ).toHexString() ) + ]; + const weiHowMuchBurn = undefined; + const gasPrice = await tc.computeGasPrice( ethersProvider, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasBurn = await tc.computeGas( + details, ethersProvider, + "ERC20", contract, "burnFrom", arrArgumentsBurn, + joAccount, strActionName, gasPrice, 10000000, weiHowMuchBurn, null ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGasBurn ); + strActionName = "Burn ERC20"; + const isIgnoreBurn = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProvider, + "ERC20", contract, "burnFrom", arrArgumentsBurn, + joAccount, strActionName, isIgnoreBurn, + gasPrice, estimatedGasBurn, weiHowMuchBurn, null ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const opts: imaTx.TCustomPayedCallOptions = + { isCheckTransactionToSchain: ( chainName !== "Mainnet" ) }; + const joReceipt = await imaTx.payedCall( + details, ethersProvider, + "ERC20", contract, "burnFrom", arrArgumentsBurn, + joAccount, strActionName, gasPrice, estimatedGasBurn, weiHowMuchBurn, opts ); + imaGasUsage.printGasUsageReportFromArray( "BURN ERC20 ", [ { + description: "burnErc20()/burn", + receipt: joReceipt + } ], details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "burnErc20", true ); + details.close(); + return joReceipt; // can be used as "true" boolean value + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "burnErc20()", false ); + details.close(); + return false; + } +} + +export async function burnErc721( + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainId: string, + chainName: string, + joAccount: state.TAccount, + idToken: any, + strTokenContractAddress: string, + joTokenContractABI: any, + tc: imaTx.TransactionCustomizer +): Promise { + let strActionName = "burnErc721() init"; + const strLogPrefix = "burnErc721() call "; + const details = log.createMemoryStream(); + try { + details.debug( "{p}Burn ERC721 token ID {}", strLogPrefix, idToken ); + if( !( ethersProvider && joAccount && + strTokenContractAddress && typeof strTokenContractAddress === "string" && + strTokenContractAddress.length > 0 && joTokenContractABI + ) ) + throw new Error( "Missing valid arguments" ); + strActionName = "burnErc721() instantiate token contract"; + const contract = new owaspUtils.ethersMod.ethers.Contract( + strTokenContractAddress, joTokenContractABI, ethersProvider ); + const arrArgumentsBurn = [ + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( idToken ).toHexString() ) + ]; + const weiHowMuchBurn = undefined; + const gasPrice = await tc.computeGasPrice( ethersProvider, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasBurn = await tc.computeGas( + details, ethersProvider, + "ERC721", contract, "burn", arrArgumentsBurn, + joAccount, strActionName, gasPrice, 10000000, weiHowMuchBurn, null ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGasBurn ); + strActionName = "Burn ERC721"; + const isIgnoreBurn = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProvider, + "ERC721", contract, "burn", arrArgumentsBurn, + joAccount, strActionName, isIgnoreBurn, + gasPrice, estimatedGasBurn, weiHowMuchBurn, null ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const opts: imaTx.TCustomPayedCallOptions = + { isCheckTransactionToSchain: ( chainName !== "Mainnet" ) }; + const joReceipt = await imaTx.payedCall( + details, ethersProvider, + "ERC721", contract, "burn", arrArgumentsBurn, + joAccount, strActionName, gasPrice, estimatedGasBurn, weiHowMuchBurn, opts ); + imaGasUsage.printGasUsageReportFromArray( "BURN ERC721 ", [ { + description: "burnErc721()/burn", + receipt: joReceipt + } ], details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "burnErc721", true ); + details.close(); + return joReceipt; // can be used as "true" boolean value + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "burnErc721()", false ); + details.close(); + return false; + } +} + +export async function burnErc1155( + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + chainId: string, + chainName: string, + joAccount: state.TAccount, + strAddressBurnFrom: string, + idToken: any, + nAmount: any, + strTokenContractAddress: string, + joTokenContractABI: any, + tc: imaTx.TransactionCustomizer +): Promise { + let strActionName = "burnErc1155() init"; + const strLogPrefix = "burnErc1155() call "; + const details = log.createMemoryStream(); + try { + details.debug( "{p}Burn ERC1155 token ID {} token amount {}", + strLogPrefix, idToken, nAmount ); + if( !( ethersProvider && joAccount && strAddressBurnFrom && + typeof strAddressBurnFrom === "string" && strAddressBurnFrom.length > 0 && + strTokenContractAddress && typeof strTokenContractAddress === "string" && + strTokenContractAddress.length > 0 && joTokenContractABI + ) ) + throw new Error( "Missing valid arguments" ); + strActionName = "burnErc1155() instantiate token contract"; + const contract = new owaspUtils.ethersMod.ethers.Contract( + strTokenContractAddress, joTokenContractABI, ethersProvider ); + const arrArgumentsBurn = [ + strAddressBurnFrom, + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( idToken ).toHexString() ), + owaspUtils.ensureStartsWith0x( owaspUtils.toBN( nAmount ).toHexString() ) + ]; + const weiHowMuchBurn = undefined; + const gasPrice = await tc.computeGasPrice( ethersProvider, 200000000000 ); + details.trace( "{p}Using computed gasPrice={}", strLogPrefix, gasPrice ); + const estimatedGasBurn = await tc.computeGas( + details, ethersProvider, + "ERC1155", contract, "burn", arrArgumentsBurn, + joAccount, strActionName, gasPrice, 10000000, weiHowMuchBurn, null ); + details.trace( "{p}Using estimated gas={}", strLogPrefix, estimatedGasBurn ); + strActionName = "Burn ERC1155"; + const isIgnoreBurn = false; + const strErrorOfDryRun = await imaTx.dryRunCall( + details, ethersProvider, + "ERC1155", contract, "burn", arrArgumentsBurn, + joAccount, strActionName, isIgnoreBurn, + gasPrice, estimatedGasBurn, weiHowMuchBurn, null ); + if( strErrorOfDryRun ) + throw new Error( strErrorOfDryRun ); + + const opts: imaTx.TCustomPayedCallOptions = + { isCheckTransactionToSchain: ( chainName !== "Mainnet" ) }; + const joReceipt = await imaTx.payedCall( + details, ethersProvider, + "ERC1155", contract, "burn", arrArgumentsBurn, + joAccount, strActionName, + gasPrice, estimatedGasBurn, weiHowMuchBurn, opts ); + imaGasUsage.printGasUsageReportFromArray( "BURN ERC1155 ", [ { + description: "burnErc1155()/burn", + receipt: joReceipt + } ], details ); + if( log.exposeDetailsGet() ) + details.exposeDetailsTo( log.globalStream(), "burnErc1155", true ); + details.close(); + return joReceipt; // can be used as "true" boolean value + } catch ( err ) { + details.critical( "{p}Payment error in {bright}: {err}, stack is:\n{stack}", + strLogPrefix, strActionName, err, err ); + details.exposeDetailsTo( log.globalStream(), "burnErc1155()", false ); + details.close(); + return false; + } +} diff --git a/src/imaTransferErrorHandling.ts b/src/imaTransferErrorHandling.ts new file mode 100644 index 00000000..72a7dd96 --- /dev/null +++ b/src/imaTransferErrorHandling.ts @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaTransferErrorHandling.ts + * @copyright SKALE Labs 2019-Present + */ + +import { UniversalDispatcherEvent, EventDispatcher } + from "./eventDispatcher.js"; + +export function verifyTransferErrorCategoryName( strCategory: string ): string { + return ( strCategory ?? "default" ); +} + +const gMaxLastTransferErrors: number = 20; +const gArrLastTransferErrors: any = []; +let gMapTransferErrorCategories: any = { }; + +export const saveTransferEvents = new EventDispatcher(); + +export function saveTransferError( strCategory: string, textLog: any, ts?: any ): void { + ts = ts || Math.round( ( new Date() ).getTime() / 1000 ); + const catName = verifyTransferErrorCategoryName( strCategory ); + const joTransferEventError: any = { + ts, + category: catName.toString(), + textLog: textLog.toString() + }; + gArrLastTransferErrors.push( joTransferEventError ); + while( gArrLastTransferErrors.length > gMaxLastTransferErrors ) + gArrLastTransferErrors.shift(); + gMapTransferErrorCategories[catName] = true; + saveTransferEvents.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { detail: joTransferEventError } ) ); +} + +export function saveTransferSuccess( strCategory: string ): void { + const catName = verifyTransferErrorCategoryName( strCategory ); + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + try { delete gMapTransferErrorCategories[catName]; } catch ( err ) { } + saveTransferEvents.dispatchEvent( + new UniversalDispatcherEvent( + "success", + { detail: { category: strCategory } } ) ); +} + +export function saveTransferSuccessAll(): void { + // clear all transfer error categories, out of time frame + gMapTransferErrorCategories = { }; +} + +export function getLastTransferErrors( isIncludeTextLog: boolean ): any[] { + if( typeof isIncludeTextLog === "undefined" ) + isIncludeTextLog = true; + const jarr = JSON.parse( JSON.stringify( gArrLastTransferErrors ) ); + if( !isIncludeTextLog ) { + for( let i = 0; i < jarr.length; ++i ) { + const jo: any = jarr[i]; + if( "textLog" in jo ) + delete jo.textLog; + } + } + return jarr; +} + +export function getLastErrorCategories(): string[] { + return Object.keys( gMapTransferErrorCategories ); +} + +let gFlagIsEnabledProgressiveEventsScan = true; + +export function getEnabledProgressiveEventsScan(): boolean { + return ( !!gFlagIsEnabledProgressiveEventsScan ); +} +export function setEnabledProgressiveEventsScan( isEnabled: boolean ): void { + gFlagIsEnabledProgressiveEventsScan = ( !!isEnabled ); +} diff --git a/src/imaTx.ts b/src/imaTx.ts new file mode 100644 index 00000000..2820aa0f --- /dev/null +++ b/src/imaTx.ts @@ -0,0 +1,765 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file imaTx.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as path from "path"; +import * as url from "url"; +import * as childProcessModule from "child_process"; +import type * as state from "./state.js"; + +import Redis from "ioredis"; +import * as ethereumJsUtilModule from "ethereumjs-util"; + +import * as log from "./log.js"; + +import * as owaspUtils from "./owaspUtils.js"; +import * as imaUtils from "./utils.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaEventLogScan from "./imaEventLogScan.js"; + +import * as threadInfo from "./threadInfo.js"; + +export interface TCustomPayedCallOptions { + isCheckTransactionToSchain?: boolean +} + +export interface TRunTimePayedCallOptions { + details: log.TLogger + ethersProvider: owaspUtils.ethersMod.providers.JsonRpcProvider + strContractName: string + joContract: owaspUtils.ethersMod.Contract + strMethodName: string + arrArguments: any[] + joAccount: state.TAccount + strActionName: string + gasPrice: any + estimatedGas: any + weiHowMuch: any + opts: TCustomPayedCallOptions + strContractCallDescription: string + strLogPrefix: string + joACI: any | null + unsignedTx: any | null + rawTx: any | null + txHash: any | null + joReceipt: any | null + callOpts: any +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +const __dirname: string = path.dirname( url.fileURLToPath( import.meta.url ) ); + +let redis: any = null; + +let gFlagDryRunIsEnabled: boolean = true; + +export function dryRunIsEnabled(): boolean { + return ( !!gFlagDryRunIsEnabled ); +} +export function dryRunEnable( isEnable: any ): boolean { + gFlagDryRunIsEnabled = ( isEnable != null && isEnable != undefined ) + ? ( !!isEnable ) + : true; + return ( !!gFlagDryRunIsEnabled ); +} + +let gFlagDryRunIsIgnored = true; + +export function dryRunIsIgnored(): boolean { + return ( !!gFlagDryRunIsIgnored ); +} + +export function dryRunIgnore( isIgnored: boolean ): boolean { + gFlagDryRunIsIgnored = ( isIgnored != null && isIgnored != undefined ) + ? ( !!isIgnored ) + : true; + return ( !!gFlagDryRunIsIgnored ); +} + +export async function dryRunCall( + details: log.TLogger, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + strContractName: string, joContract: owaspUtils.ethersMod.ethers.Contract, + strMethodName: string, arrArguments: any[], + joAccount: state.TAccount, strActionName: string, isDryRunResultIgnore: boolean, + gasPrice: any, gasValue: any, weiHowMuch?: any, + opts?: any +): Promise { + if( !dryRunIsEnabled() ) + return null; // success + isDryRunResultIgnore = ( isDryRunResultIgnore != null && isDryRunResultIgnore != undefined ) + ? ( !!isDryRunResultIgnore ) + : false; + const strContractMethodDescription = log.fmtDebug( "{p}({}).{sunny}", + strContractName, joContract.address, strMethodName ); + let strArgumentsDescription = ""; + if( arrArguments.length > 0 ) { + strArgumentsDescription += log.fmtDebug( "( " ); + for( let i = 0; i < arrArguments.length; ++i ) { + if( i > 0 ) + strArgumentsDescription += log.fmtDebug( ", " ); + strArgumentsDescription += log.fmtInformation( "{}", arrArguments[i] ); + } + strArgumentsDescription += log.fmtDebug( " )" ); + } else + strArgumentsDescription += log.fmtDebug( "()" ); + const strContractCallDescription = strContractMethodDescription + strArgumentsDescription; + const strLogPrefix = `${strContractMethodDescription} `; + try { + details.trace( "Dry-run of action {bright}...", strActionName ); + details.trace( "Will dry-run {}...", strContractCallDescription ); + const strAccountWalletAddress = joAccount.address(); + const callOpts: any = { + from: strAccountWalletAddress + }; + if( gasPrice ) + callOpts.gasPrice = owaspUtils.toBN( gasPrice ).toHexString(); + if( gasValue ) + callOpts.gasLimit = owaspUtils.toBN( gasValue ).toHexString(); + if( weiHowMuch ) + callOpts.value = owaspUtils.toBN( weiHowMuch ).toHexString(); + const joDryRunResult = + await joContract.callStatic[strMethodName]( ...arrArguments, callOpts ); + details.trace( "{p}dry-run success: {}", strLogPrefix, joDryRunResult ); + return null; // success + } catch ( err ) { + const strError = owaspUtils.extractErrorMessage( err ); + details.error( "{p}dry-run error: {err}", strLogPrefix, err ); + if( dryRunIsIgnored() ) + return null; + return strError; + } +} + +async function payedCallPrepare( optsPayedCall: TRunTimePayedCallOptions ): Promise { + optsPayedCall.joACI = getAccountConnectivityInfo( optsPayedCall.joAccount ); + if( optsPayedCall.gasPrice ) { + optsPayedCall.callOpts.gasPrice = + owaspUtils.toBN( optsPayedCall.gasPrice ).toHexString(); + } + if( optsPayedCall.estimatedGas ) { + optsPayedCall.callOpts.gasLimit = + owaspUtils.toBN( optsPayedCall.estimatedGas ).toHexString(); + } + if( optsPayedCall.weiHowMuch ) { + optsPayedCall.callOpts.value = + owaspUtils.toBN( optsPayedCall.weiHowMuch ).toHexString(); + } + optsPayedCall.details.trace( + "{p}payed-call of action {bright} will do payed-call {p} with call options {} " + + "via {sunny}-sign-and-send using from address {}...", optsPayedCall.strLogPrefix, + optsPayedCall.strActionName, optsPayedCall.strContractCallDescription, + optsPayedCall.callOpts, optsPayedCall.joACI.strType, optsPayedCall.joAccount.address() ); + optsPayedCall.unsignedTx = + await optsPayedCall.joContract.populateTransaction[optsPayedCall.strMethodName]( + ...optsPayedCall.arrArguments, optsPayedCall.callOpts ); + optsPayedCall.unsignedTx.nonce = owaspUtils.toBN( + await optsPayedCall.ethersProvider.getTransactionCount( + optsPayedCall.joAccount.address() ) ); + if( optsPayedCall.opts?.isCheckTransactionToSchain ) { + optsPayedCall.unsignedTx = await checkTransactionToSchain( + optsPayedCall.unsignedTx, optsPayedCall.details, + optsPayedCall.ethersProvider, optsPayedCall.joAccount ); + } + optsPayedCall.details.trace( "{p}populated transaction: {}", optsPayedCall.strLogPrefix, + optsPayedCall.unsignedTx ); + optsPayedCall.rawTx = + owaspUtils.ethersMod.ethers.utils.serializeTransaction( optsPayedCall.unsignedTx ); + optsPayedCall.details.trace( "{p}taw transaction: {}", optsPayedCall.strLogPrefix, + optsPayedCall.rawTx ); + optsPayedCall.txHash = owaspUtils.ethersMod.ethers.utils.keccak256( optsPayedCall.rawTx ); + optsPayedCall.details.trace( "{p}transaction hash: {}", optsPayedCall.strLogPrefix, + optsPayedCall.txHash ); +} + +async function payedCallTM( optsPayedCall: TRunTimePayedCallOptions ): Promise { + const txAdjusted: any = + optsPayedCall.unsignedTx; // JSON.parse( JSON.stringify( optsPayedCall.rawTx ) ); + const arrNamesConvertToHex = [ "gas", "gasLimit", "optsPayedCall.gasPrice", "value" ]; + for( let idxName = 0; idxName < arrNamesConvertToHex.length; ++idxName ) { + const strName = arrNamesConvertToHex[idxName]; + if( strName in txAdjusted && typeof txAdjusted[strName] === "object" && + typeof txAdjusted[strName].toHexString === "function" ) + txAdjusted[strName] = owaspUtils.toHexStringSafe( txAdjusted[strName] ); + } + if( "gasLimit" in txAdjusted ) + delete txAdjusted.gasLimit; + if( "chainId" in txAdjusted ) + delete txAdjusted.chainId; + const { chainId } = await optsPayedCall.ethersProvider.getNetwork(); + txAdjusted.chainId = chainId; + optsPayedCall.details.trace( "{p}Adjusted transaction: {}", optsPayedCall.strLogPrefix, + txAdjusted ); + if( redis == null ) + redis = new Redis( optsPayedCall.joAccount.strTransactionManagerURL ); + const priority = optsPayedCall.joAccount.nTmPriority || 5; + optsPayedCall.details.trace( "{p}TM priority: {}", optsPayedCall.strLogPrefix, priority ); + try { + const [ idTransaction, joReceiptFromTM ] = await tmEnsureTransaction( + optsPayedCall.details, optsPayedCall.ethersProvider, priority, txAdjusted ); + optsPayedCall.joReceipt = joReceiptFromTM; + optsPayedCall.details.trace( "{p}ID of TM-transaction: {}", + optsPayedCall.strLogPrefix, idTransaction ); + const txHashSent = optsPayedCall.joReceipt.transactionHash; + optsPayedCall.details.trace( "{p}Hash of sent TM-transaction: {}", + optsPayedCall.strLogPrefix, txHashSent ); + return optsPayedCall.joReceipt; + } catch ( err ) { + optsPayedCall.details.critical( + "{p}TM-transaction was not sent, underlying error is: {err}", + optsPayedCall.strLogPrefix, err ); + throw err; + } +} + +async function payedCallSGX( optsPayedCall: TRunTimePayedCallOptions ): Promise { + const tx = optsPayedCall.unsignedTx; + let { chainId } = await optsPayedCall.ethersProvider.getNetwork(); + if( typeof chainId === "string" && chainId ) + chainId = owaspUtils.parseIntOrHex( chainId ); + optsPayedCall.details.trace( "{p}Chain ID is: {}", + optsPayedCall.strLogPrefix, chainId ); + const strCmd = process.argv[0] + " --no-warnings ./imaSgxExternalSigner.js " + + ( log.isEnabledColorization() ? "true" : "false" ) + " " + + "\"" + optsPayedCall.joAccount.strSgxURL + "\" " + + "\"" + optsPayedCall.joAccount.strSgxKeyName + "\" " + + "\"" + owaspUtils.ethersProviderToUrl( optsPayedCall.ethersProvider ) + "\" " + + "\"" + chainId + "\" " + + "\"" + ( tx.data ? tx.data : "" ) + "\" " + + "\"" + tx.to + "\" " + + "\"" + owaspUtils.toHexStringSafe( tx.value ) + "\" " + + "\"" + owaspUtils.toHexStringSafe( tx.gasPrice ) + "\" " + + "\"" + owaspUtils.toHexStringSafe( tx.gasLimit ) + "\" " + + "\"" + owaspUtils.toHexStringSafe( tx.nonce ) + "\" " + + "\"" + ( optsPayedCall.joAccount.strPathSslCert + ? optsPayedCall.joAccount.strPathSslCert + : "" ) + "\" " + + "\"" + ( optsPayedCall.joAccount.strPathSslKey + ? optsPayedCall.joAccount.strPathSslKey + : "" ) + "\" " + + ""; + const joSpawnOptions: any = { + shell: true, + cwd: __dirname, + env: {}, + encoding: "utf-8" + }; + const rv = childProcessModule.spawnSync( strCmd, joSpawnOptions ); + const strStdOutFromExternalInvocation = rv.stdout.toString(); + optsPayedCall.joReceipt = JSON.parse( strStdOutFromExternalInvocation.toString() ); + optsPayedCall.details.trace( "{p}Result from external SGX signer is: {}", + optsPayedCall.strLogPrefix, optsPayedCall.joReceipt ); + postConvertBN( optsPayedCall.joReceipt, "gasUsed" ); + postConvertBN( optsPayedCall.joReceipt, "cumulativeGasUsed" ); + postConvertBN( optsPayedCall.joReceipt, "effectiveGasPrice" ); +} + +function postConvertBN( jo: any, name: any ): void { + if( !jo ) + return; + if( !( name in jo ) ) + return; + if( typeof jo[name] === "object" ) + return; + jo[name] = owaspUtils.toBN( jo[name] ); +} + +async function payedCallDirect( optsPayedCall: TRunTimePayedCallOptions ): Promise { + const ethersWallet = new owaspUtils.ethersMod.ethers.Wallet( + owaspUtils.ensureStartsWith0x( optsPayedCall.joAccount.privateKey ), + optsPayedCall.ethersProvider ); + let { chainId } = await optsPayedCall.ethersProvider.getNetwork(); + if( typeof chainId === "string" && chainId ) + chainId = owaspUtils.parseIntOrHex( chainId ); + optsPayedCall.details.trace( "{p}Chain ID is: {}", optsPayedCall.strLogPrefix, chainId ); + if( ( !( chainId in optsPayedCall.unsignedTx ) ) || + ( !optsPayedCall.unsignedTx.chainId ) + ) { + optsPayedCall.unsignedTx.chainId = chainId; + optsPayedCall.details.trace( "{p}TX with chainId: {}", + optsPayedCall.strLogPrefix, optsPayedCall.unsignedTx ); + } + const joSignedTX = await ethersWallet.signTransaction( optsPayedCall.unsignedTx ); + optsPayedCall.details.trace( "{p}Signed transaction: {}", optsPayedCall.strLogPrefix, + joSignedTX ); + const sr = await optsPayedCall.ethersProvider.sendTransaction( + owaspUtils.ensureStartsWith0x( joSignedTX ) ); + optsPayedCall.details.trace( "{p}Raw-sent transaction result: {}", + optsPayedCall.strLogPrefix, sr ); + optsPayedCall.joReceipt = + await optsPayedCall.ethersProvider.waitForTransaction( sr.hash ); + optsPayedCall.details.trace( "{p}Transaction receipt: {}", optsPayedCall.strLogPrefix, + optsPayedCall.joReceipt ); +} + +export async function payedCall( + details: log.TLogger, ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + strContractName: string, joContract: owaspUtils.ethersMod.ethers.Contract, + strMethodName: any, arrArguments: any[], + joAccount: state.TAccount, strActionName: string, + gasPrice: any, estimatedGas: any, weiHowMuch?: any, + opts?: any +): Promise { + const optsPayedCall: TRunTimePayedCallOptions = { + details, + ethersProvider, + strContractName, + joContract, + strMethodName, + arrArguments, + joAccount, + strActionName, + gasPrice, + estimatedGas, + weiHowMuch, + opts, + strContractCallDescription: "", + strLogPrefix: "", + joACI: null, + unsignedTx: null, + rawTx: null, + txHash: null, + joReceipt: null, + callOpts: { + } + }; + const strContractMethodDescription = log.fmtDebug( "{p}({}).{sunny}", + optsPayedCall.strContractName, optsPayedCall.joContract.address, + optsPayedCall.strMethodName ); + let strArgumentsDescription = ""; + if( optsPayedCall.arrArguments.length > 0 ) { + strArgumentsDescription += log.fmtDebug( "( " ); + for( let i = 0; i < optsPayedCall.arrArguments.length; ++i ) { + if( i > 0 ) + strArgumentsDescription += log.fmtDebug( ", " ); + strArgumentsDescription += log.fmtInformation( "{}", optsPayedCall.arrArguments[i] ); + } + strArgumentsDescription += log.fmtDebug( " )" ); + } else + strArgumentsDescription += log.fmtDebug( "()" ); + optsPayedCall.strContractCallDescription = + strContractMethodDescription + strArgumentsDescription; + optsPayedCall.strLogPrefix = `${strContractMethodDescription} `; + try { + await payedCallPrepare( optsPayedCall ); + switch ( optsPayedCall.joACI.strType ) { + case "tm": + await payedCallTM( optsPayedCall ); + break; + case "sgx": + await payedCallSGX( optsPayedCall ); + break; + case "direct": + await payedCallDirect( optsPayedCall ); + break; + default: { + const strErrorPrefix = "Transaction sign and send error(INNER FLOW): "; + optsPayedCall.details.critical( + "{p}bad credentials information specified, no explicit SGX and no explicit " + + "private key found", strErrorPrefix ); + throw new Error( `${strErrorPrefix} bad credentials information specified, ` + + "no explicit SGX and no explicit private key found" ); + } // NOTICE: "break;" is not needed here because of "throw" above + } // switch( optsPayedCall.joACI.strType ) + } catch ( err ) { + const strErrorPrefix = "Transaction sign and send error(outer flow):"; + optsPayedCall.details.critical( "{p}{} {err}, stack is:\n{stack}", + optsPayedCall.strLogPrefix, strErrorPrefix, err, err ); + throw new Error( `${strErrorPrefix} invoking ` + + `the ${optsPayedCall.strContractCallDescription}, ` + + `error is: ${owaspUtils.extractErrorMessage( err )}` ); + } + optsPayedCall.details.success( "{p}Done, TX was {sunny}-signed-and-sent, receipt is {}", + optsPayedCall.strLogPrefix, optsPayedCall.joACI ? optsPayedCall.joACI.strType : "N/A", + optsPayedCall.joReceipt ); + try { + const bnGasSpent = owaspUtils.toBN( optsPayedCall.joReceipt.cumulativeGasUsed ); + const gasSpent = bnGasSpent.toString(); + const ethSpent = owaspUtils.ethersMod.ethers.utils.formatEther( + optsPayedCall.joReceipt.cumulativeGasUsed.mul( optsPayedCall.unsignedTx.gasPrice ) ); + optsPayedCall.joReceipt.summary = { + bnGasSpent, + gasSpent, + ethSpent + }; + optsPayedCall.details.trace( "{p}gas spent: {}", optsPayedCall.strLogPrefix, gasSpent ); + optsPayedCall.details.trace( "{p}ETH spent: {}", optsPayedCall.strLogPrefix, ethSpent ); + } catch ( err ) { + optsPayedCall.details.warning( "{p}TX stats computation error {err}, stack is:\n{stack}", + optsPayedCall.strLogPrefix, err, err ); + } + return optsPayedCall.joReceipt; +} + +export async function checkTransactionToSchain( + unsignedTx: any, + details: log.TLogger, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + joAccount: state.TAccount +): Promise { + const strLogPrefix = "PoW-mining: "; + try { + const strFromAddress = joAccount.address(); // unsignedTx.from; + const requiredBalance = unsignedTx.gasPrice.mul( unsignedTx.gasLimit ); + const balance = owaspUtils.toBN( await ethersProvider.getBalance( strFromAddress ) ); + details.trace( + "{p}Will check whether PoW-mining is needed for sender {} with balance {} using " + + "required balance {}, gas limit is {} gas, checked unsigned transaction is {}", + strLogPrefix, strFromAddress, owaspUtils.toHexStringSafe( balance ), + owaspUtils.toHexStringSafe( requiredBalance ), + owaspUtils.toHexStringSafe( unsignedTx.gasLimit ), unsignedTx + ); + if( balance.lt( requiredBalance ) ) { + details.warning( "{p}Insufficient funds for {}, will run PoW-mining to get {} of gas", + strLogPrefix, strFromAddress, owaspUtils.toHexStringSafe( unsignedTx.gasLimit ) ); + const powNumberBuffer = await calculatePowNumber( + strFromAddress, owaspUtils.toBN( unsignedTx.nonce ).toHexString(), + owaspUtils.toHexStringSafe( unsignedTx.gasLimit ), details, strLogPrefix ); + details.debug( "{p}Returned PoW-mining number {}", strLogPrefix, powNumberBuffer ); + let powNumber: string = powNumberBuffer.toString( "utf8" ).trim(); + powNumber = imaUtils.replaceAll( powNumber, "\r", "" ); + powNumber = imaUtils.replaceAll( powNumber, "\n", "" ); + powNumber = imaUtils.replaceAll( powNumber, "\t", "" ); + powNumber = powNumber.trim(); + details.trace( "{p}Trimmed PoW-mining number is {}", strLogPrefix, powNumber ); + if( !powNumber ) + throw new Error( "Failed to compute gas price with PoW-mining(1), got empty text" ); + powNumber = owaspUtils.toBN( owaspUtils.ensureStartsWith0x( powNumber ) ); + details.trace( "{p}BN PoW-mining number is {}", strLogPrefix, powNumber ); + const powNumberBN = owaspUtils.toBN( powNumber ); + if( powNumberBN.eq( owaspUtils.toBN( "0" ) ) ) + throw new Error( "Failed to compute gas price with PoW-mining(2), got zero value" ); + unsignedTx.gasPrice = owaspUtils.toBN( powNumberBN.toHexString() ); + details.success( "{p}Success, finally (after PoW-mining) modified unsigned " + + "transaction is {}", strLogPrefix, unsignedTx ); + } else { + details.success( "{p}Have sufficient funds for {}, PoW-mining is not needed and " + + "will be skipped", strLogPrefix, strFromAddress ); + } + } catch ( err ) { + details.critical( "{p}PoW-mining error(checkTransactionToSchain): exception occur before " + + "PoW-mining, error is: {err}, stack is:\n{stack}", strLogPrefix, err, err ); + } + return unsignedTx; +} + +export async function calculatePowNumber( + address: string, nonce: any, gas: any, details: log.TLogger, strLogPrefix: string +): Promise { + try { + let _address = owaspUtils.ensureStartsWith0x( address ); + _address = ethereumJsUtilModule.toChecksumAddress( _address ); + _address = owaspUtils.removeStarting0x( _address ); + const _nonce = owaspUtils.parseIntOrHex( nonce ); + const _gas = owaspUtils.parseIntOrHex( gas ); + const powScriptPath = path.join( __dirname, "pow" ); + const cmd = `${powScriptPath} ${_address} ${_nonce} ${_gas}`; + details.trace( "{p}Will run PoW-mining command: {}", strLogPrefix, cmd ); + const res = childProcessModule.execSync( cmd ); + details.trace( "{p}Got PoW-mining execution result: {}", strLogPrefix, res ); + return res; + } catch ( err ) { + details.critical( "{p}PoW-mining error(calculatePowNumber): exception occur during " + + "PoW-mining, error is: {err}, stack is:\n{stack}", strLogPrefix, err, err ); + throw err; + } +} + +export function getAccountConnectivityInfo( joAccount: state.TAccount ): object { + const joACI: any = { + isBad: true, + strType: "bad", + isAutoSend: false + }; + if( "strTransactionManagerURL" in joAccount && + typeof joAccount.strTransactionManagerURL === "string" && + joAccount.strTransactionManagerURL.length > 0 + ) { + joACI.isBad = false; + joACI.strType = "tm"; + joACI.isAutoSend = true; + } else if( "strSgxURL" in joAccount && + typeof joAccount.strSgxURL === "string" && + joAccount.strSgxURL.length > 0 && + "strSgxKeyName" in joAccount && + typeof joAccount.strSgxKeyName === "string" && + joAccount.strSgxKeyName.length > 0 + ) { + joACI.isBad = false; + joACI.strType = "sgx"; + } else if( "privateKey" in joAccount && + typeof joAccount.privateKey === "string" && + joAccount.privateKey.length > 0 + ) { + joACI.isBad = false; + joACI.strType = "direct"; + } else { + // bad by default + } + return joACI; +} + +const gTransactionManagerPool = "transactions"; + +function tmGenerateRandomHex( size: number ): string { + return [ ...Array( size ) ] + .map( () => Math.floor( Math.random() * 16 ).toString( 16 ) ).join( "" ); +} + +function tmMakeId( details: log.TLogger ): string { + const prefix = "tx-"; + const unique = tmGenerateRandomHex( 16 ); + const id = prefix + unique + "js"; + details.trace( "TM - Generated id: {}", id ); + return id; +} + +function tmMakeRecord( tx: any = {}, score: any ): any { + const status = "PROPOSED"; + return JSON.stringify( { + score, + status, + ...tx + } ); +} + +function tmMakeScore( priority: number ): number { + const ts = imaHelperAPIs.currentTimestamp(); + return priority * Math.pow( 10, ts.toString().length ) + ts; +} + +async function tmSend( details: log.TLogger, tx: any, priority: number = 5 ): Promise { + details.trace( "TM - sending tx {} ts: {}", tx, imaHelperAPIs.currentTimestamp() ); + const id = tmMakeId( details ); + const score = tmMakeScore( priority ); + const record = tmMakeRecord( tx, score ); + details.trace( "TM - Sending score: {}, record: {}", score, record ); + const expiration = 24 * 60 * 60; // 1 day; + await redis.multi() + .set( id, record, "EX", expiration ) + .zadd( gTransactionManagerPool, score, id ) + .exec(); + return id; +} + +function tmIsFinished( record: any ): boolean { + if( !record ) + return false; + return [ "SUCCESS", "FAILED", "DROPPED" ].includes( record.status ); +} + +async function tmGetRecord( txId: any ): Promise { + const r = await redis.get( txId ); + if( r != null ) + return JSON.parse( r ); + return null; +} + +async function tmWait( + details: log.TLogger, + txId: any, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + nWaitSeconds: number = 36000 ): Promise { + const strLogPrefix = log.fmtDebug( "(gathered details)" ) + " "; + details.debug( "{p}TM - will wait TX {} to complete for {} second(s) maximum", + strLogPrefix, txId, nWaitSeconds ); + const startTs = imaHelperAPIs.currentTimestamp(); + while( !tmIsFinished( await tmGetRecord( txId ) ) && + ( imaHelperAPIs.currentTimestamp() - startTs ) < nWaitSeconds ) + await threadInfo.sleep( 500 ); + const r = await tmGetRecord( txId ); + details.debug( "{p}TM - TX {} record is {}", strLogPrefix, txId, r ); + if( ( !r ) ) + details.error( "{p}TM - TX {} status is NULL RECORD", strLogPrefix, txId ); + else if( r.status == "SUCCESS" ) + details.success( "{p}TM - TX {} success", strLogPrefix, txId ); + else + details.error( "{p}TM - TX {} status is {err}", strLogPrefix, txId, r.status ); + + if( ( !tmIsFinished( r ) ) || r.status == "DROPPED" ) { + details.error( "{p}TM - TX {} was unsuccessful, wait failed", strLogPrefix, txId ); + return null; + } + const joReceipt: any = await imaEventLogScan.safeGetTransactionReceipt( + details, 10, ethersProvider, r.tx_hash ); + if( !joReceipt ) { + details.error( "{p}TM - TX {} was unsuccessful, failed to fetch transaction receipt", + strLogPrefix, txId ); + return null; + } + return joReceipt; +} + +async function tmEnsureTransaction( + details: log.TLogger, ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + priority: any, txAdjusted: any, + cntAttempts?: number, sleepMilliseconds?: number +): Promise { + cntAttempts = cntAttempts ?? 1; + sleepMilliseconds = sleepMilliseconds ?? ( 30 * 1000 ); + let txId = ""; + let joReceipt = null; + let idxAttempt = 0; + const strLogPrefix = log.fmtDebug( "(gathered details)" ) + " "; + for( ; idxAttempt < cntAttempts; ++idxAttempt ) { + txId = await tmSend( details, txAdjusted, priority ); + details.debug( "{p}TM - next TX {}", strLogPrefix, txId ); + joReceipt = await tmWait( details, txId, ethersProvider ); + if( joReceipt ) + break; + details.error( "{p}TM - unsuccessful TX {} sending attempt {} of {} receipt: {}", + strLogPrefix, txId, idxAttempt, cntAttempts, joReceipt ); + await threadInfo.sleep( sleepMilliseconds ); + } + if( !joReceipt ) { + details.error( "{p}TM TX {} transaction has been dropped", strLogPrefix, txId ); + throw new Error( `TM unsuccessful transaction ${txId}` ); + } + details.information( "{p}TM - successful TX {}, sending attempt {} of {}", + strLogPrefix, txId, idxAttempt, cntAttempts ); + return [ txId, joReceipt ]; +} + +export class TransactionCustomizer { + gasPriceMultiplier: any; + gasMultiplier: any; + constructor ( gasPriceMultiplier: any, gasMultiplier: any ) { + this.gasPriceMultiplier = gasPriceMultiplier + ? owaspUtils.toFloat( gasPriceMultiplier ) + : null; // null means use current gasPrice or recommendedGasPrice + this.gasMultiplier = gasMultiplier ? owaspUtils.toFloat( gasMultiplier ) : 1.25; + } + + async computeGasPrice( + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + maxGasPrice: any ): Promise { + const gasPrice = + owaspUtils.parseIntOrHex( + owaspUtils.toBN( + await ethersProvider.getGasPrice() ).toHexString() ); + if( gasPrice == 0 || + gasPrice == null || + gasPrice == undefined || + gasPrice <= 1000000000 + ) + return owaspUtils.toBN( "1000000000" ).toHexString(); + else if( + this.gasPriceMultiplier != null && + this.gasPriceMultiplier != undefined && + this.gasPriceMultiplier >= 0 && + maxGasPrice != null && + maxGasPrice != undefined + ) { + let gasPriceMultiplied = gasPrice * this.gasPriceMultiplier; + if( gasPriceMultiplied > maxGasPrice ) + gasPriceMultiplied = maxGasPrice; + return owaspUtils.toBN( maxGasPrice ); + } else + return gasPrice; + } + + async computeGas( + details: log.TLogger, + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider, + strContractName: string, joContract: owaspUtils.ethersMod.ethers.Contract, + strMethodName: string, arrArguments: any[], + joAccount: state.TAccount, strActionName: string, + gasPrice: any, gasValueRecommended: any, weiHowMuch?: any, + opts?: any + ): Promise { + let estimatedGas: any = 0; + const strContractMethodDescription = log.fmtDebug( "{p}({}).{sunny}", + strContractName, joContract.address, strMethodName ); + let strArgumentsDescription = ""; + if( arrArguments.length > 0 ) { + strArgumentsDescription += log.fmtDebug( "( " ); + for( let i = 0; i < arrArguments.length; ++i ) { + if( i > 0 ) + strArgumentsDescription += log.fmtDebug( ", " ); + strArgumentsDescription += log.fmtInformation( "{}", arrArguments[i] ); + } + strArgumentsDescription += log.fmtDebug( " )" ); + } else + strArgumentsDescription += log.fmtDebug( "()" ); + const strContractCallDescription = + strContractMethodDescription + strArgumentsDescription; + const strLogPrefix = `${strContractMethodDescription} `; + try { + details.trace( "Estimate-gas of action {bright}...", strActionName ); + details.trace( "Will estimate-gas {}...", strContractCallDescription ); + const strAccountWalletAddress = joAccount.address(); + const callOpts: any = { from: strAccountWalletAddress }; + if( gasPrice ) + callOpts.gasPrice = owaspUtils.toBN( gasPrice ).toHexString(); + if( gasValueRecommended ) + callOpts.gasLimit = owaspUtils.toBN( gasValueRecommended ).toHexString(); + if( weiHowMuch ) + callOpts.value = owaspUtils.toBN( weiHowMuch ).toHexString(); + details.trace( "Call options for estimate-gas {}", callOpts ); + estimatedGas = await joContract.estimateGas[strMethodName]( ...arrArguments, callOpts ); + details.success( "{p}estimate-gas success: {}", strLogPrefix, estimatedGas ); + } catch ( err ) { + details.error( + "{p}Estimate-gas error: {err}, default recommended gas value will be used " + + "instead of estimated, stack is:\n{stack}", strLogPrefix, err, err ); + } + estimatedGas = owaspUtils.parseIntOrHex( owaspUtils.toBN( estimatedGas ).toString() ); + if( estimatedGas == 0 ) { + estimatedGas = gasValueRecommended; + details.warning( "{p}Will use recommended gas {} instead of estimated", + strLogPrefix, estimatedGas ); + } + if( this.gasMultiplier > 0.0 ) { + estimatedGas = + owaspUtils.parseIntOrHex( ( estimatedGas * this.gasMultiplier ).toString() ); + } + details.trace( "{p}Final amount of gas is {}", strLogPrefix, estimatedGas ); + return estimatedGas; + } +}; + +let gTransactionCustomizerMainNet: TransactionCustomizer | null = null; +let gTransactionCustomizerSChain: TransactionCustomizer | null = null; +let gTransactionCustomizerSChainTarget: TransactionCustomizer | null = null; + +export function getTransactionCustomizerForMainNet(): TransactionCustomizer { + if( gTransactionCustomizerMainNet ) + return gTransactionCustomizerMainNet; + gTransactionCustomizerMainNet = new TransactionCustomizer( 1.25, 1.25 ); + return gTransactionCustomizerMainNet; +} + +export function getTransactionCustomizerForSChain(): TransactionCustomizer { + if( gTransactionCustomizerSChain ) + return gTransactionCustomizerSChain; + gTransactionCustomizerSChain = new TransactionCustomizer( null, 1.25 ); + return gTransactionCustomizerSChain; +} + +export function getTransactionCustomizerForSChainTarget(): TransactionCustomizer { + if( gTransactionCustomizerSChainTarget ) + return gTransactionCustomizerSChainTarget; + gTransactionCustomizerSChainTarget = new TransactionCustomizer( null, 1.25 ); + return gTransactionCustomizerSChainTarget; +} diff --git a/src/log.ts b/src/log.ts new file mode 100644 index 00000000..f100383f --- /dev/null +++ b/src/log.ts @@ -0,0 +1,1186 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file log.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as cc from "./cc.js"; +import * as fs from "fs"; + +export { cc }; + +export type TFunctionWrite = ( ...args: any[] ) => void; +export type TFunctionClose = () => void; +export type TFunctionOpen = () => void; +export type TFunctionSize = () => number; +export type TFunctionRotate = ( nBytesToWrite: number ) => void; +export type TFunctionToString = () => string; +export type TFunctionExposeDetailsTo = + ( otherStream: TLogger, strTitle: string, isSuccess: boolean ) => void; + +export interface TLogger { + id: number + strPath: string + nMaxSizeBeforeRotation: number + nMaxFilesCount: number + objStream: any | null + haveOwnTimestamps: boolean + isPausedTimeStamps: boolean + strOwnIndent: string + write: TFunctionWrite + writeRaw: TFunctionWrite + close: TFunctionClose + open: TFunctionOpen + size: TFunctionSize + rotate: TFunctionRotate + toString: TFunctionToString + exposeDetailsTo: TFunctionExposeDetailsTo + // high-level formatters + fatal: TFunctionWrite + critical: TFunctionWrite + error: TFunctionWrite + warning: TFunctionWrite + attention: TFunctionWrite + information: TFunctionWrite + info: TFunctionWrite + notice: TFunctionWrite + note: TFunctionWrite + debug: TFunctionWrite + trace: TFunctionWrite + success: TFunctionWrite +} + +export type TFunctionIsBeginningOfAccumulatedLog = () => boolean; +export type TFunctionIsLastLineEndsWithCarriageReturn = () => boolean; +export type TFunctionClear = () => void; + +export interface TLoggerMemory extends TLogger { + arrAccumulatedLogTextLines: string[] + isBeginningOfAccumulatedLog: TFunctionIsBeginningOfAccumulatedLog + isLastLineEndsWithCarriageReturn: TFunctionIsLastLineEndsWithCarriageReturn + clear: TFunctionClear +}; + +let gArrStreams: TLogger[] = []; + +let gFlagLogWithTimeStamps: boolean = true; + +let gIdentifierAllocatorCounter = 1; + +const safeURL = cc.safeURL; +const replaceAll = cc.replaceAll; +const timestampHR = cc.timestampHR; +const capitalizeFirstLetter = cc.capitalizeFirstLetter; +const getDurationString = cc.getDurationString; + +export { safeURL, replaceAll, timestampHR, capitalizeFirstLetter, getDurationString }; + +export function validateRadix( value: any, radix?: any ): boolean { + value = ( value ? value.toString() : "10" ).trim(); + radix = ( radix === null || radix === undefined ) + ? ( ( value.length > 2 && value[0] == "0" && ( value[1] == "x" || value[1] == "X" ) ) + ? 16 + : 10 ) + : parseInt( radix, 10 ); + return radix; +} + +export function validateInteger( value: any, radix?: any ): boolean { + try { + if( value === null || value === undefined ) + return false; + if( value === 0 || value === 0.0 ) + return true; + const s = value ? value.toString().trim() : ""; + if( s.length < 1 ) + return false; + radix = validateRadix( value, radix ); + if( ( !isNaN( value ) ) && + ( parseInt( value, radix ) == value || radix !== 10 ) && + ( !isNaN( parseInt( value, radix ) ) ) + ) + return true; + } catch ( err ) { + } + return false; +} + +export function toInteger( value: any, radix?: any ): number { + try { + if( value === 0 || value === 0.0 || value === null || value === undefined ) + return 0; + value = ( value ? value.toString().trim() : "" ).trim(); + radix = validateRadix( value, radix ); + if( !validateInteger( value, radix ) ) + return NaN; + return parseInt( value.toString().trim(), radix ); + } catch ( err ) { + } + return 0; +} + +export function autoEnableColorizationFromCommandLineArgs(): void { + cc.autoEnableFromCommandLineArgs(); +} +export function enableColorization( bIsEnable?: boolean ): void { + cc.enable( !!bIsEnable ); +} +export function isEnabledColorization(): boolean { + return ( !!( cc.isEnabled() ) ); +} + +export function getPrintTimestamps(): boolean { + return gFlagLogWithTimeStamps; +} + +export function setPrintTimestamps( b?: boolean ): void { + gFlagLogWithTimeStamps = ( !!b ); +} + +export function n2s( n: any, sz: number ): string { + let s: string = n ?? ""; + while( s.length < sz ) + s = "0" + s; + return s; +} + +export function generateTimestampString( ts?: any, isColorized?: boolean ): string { + isColorized = + ( typeof isColorized === "undefined" ) + ? true + : ( !!isColorized ); + ts = ( ts instanceof Date ) ? ts : new Date(); + const ccDate = function( x?: any ): string { return isColorized ? cc.date( x ) : x; }; + const ccTime = function( x?: any ): string { return isColorized ? cc.time( x ) : x; }; + const ccFractionPartOfTime = + function( x?: any ): string { return isColorized ? cc.fracTime( x ) : x; }; + const ccBright = function( x?: any ): string { return isColorized ? cc.bright( x ) : x; }; + const s = + ccDate( n2s( ts.getUTCFullYear(), 4 ) ) + + ccBright( "-" ) + ccDate( n2s( ts.getUTCMonth() + 1, 2 ) ) + + ccBright( "-" ) + ccDate( n2s( ts.getUTCDate(), 2 ) ) + + " " + ccTime( n2s( ts.getUTCHours(), 2 ) ) + + ccBright( ":" ) + ccTime( n2s( ts.getUTCMinutes(), 2 ) ) + + ccBright( ":" ) + ccTime( n2s( ts.getUTCSeconds(), 2 ) ) + + ccBright( "." ) + ccFractionPartOfTime( n2s( ts.getUTCMilliseconds(), 3 ) ); + + return s; +} + +export function generateTimestampPrefix( ts?: any, isColorized?: boolean ): string { + return generateTimestampString( ts, isColorized ) + cc.bright( ":" ) + " "; +} + +export function removeAllStreams(): void { + let i = 0; let cnt = 0; + try { + cnt = gArrStreams.length; + for( i = 0; i < cnt; ++i ) { + try { + const objEntry = gArrStreams[i]; + objEntry.objStream.close(); + } catch ( err ) { + } + } + } catch ( err ) { + } + gArrStreams = []; +} + +export function getStreamWithFilePath( strFilePath: string ): TLogger | null { + try { + let i = 0; const cnt = gArrStreams.length; + for( i = 0; i < cnt; ++i ) { + try { + const objEntry = gArrStreams[i]; + if( objEntry.strPath === strFilePath ) + return objEntry; + } catch ( err ) { + } + } + } catch ( err ) { + } + return null; +} + +let gStreamGlobal: TLogger | null; + +export function globalStream(): TLogger { + if( !gStreamGlobal ) { + gStreamGlobal = { + id: 0, + strPath: "global", + nMaxSizeBeforeRotation: -1, + nMaxFilesCount: -1, + objStream: null, + haveOwnTimestamps: false, + isPausedTimeStamps: false, + strOwnIndent: "", + write, + writeRaw, + close: function(): void { }, + open: function(): void { }, + size: function(): number { return 0; }, + rotate: function( nBytesToWrite: number ) { }, + toString: function(): string { return ""; }, + exposeDetailsTo, + // high-level formatters + fatal, + critical, + error, + warning, + attention, + information, + info, + notice, + note, + debug, + trace, + success + }; + } + return gStreamGlobal; +}; + +export function createStandardOutputStream(): TLogger | null { + try { + const objEntry: TLogger = { + id: gIdentifierAllocatorCounter++, + strPath: "stdout", + nMaxSizeBeforeRotation: -1, + nMaxFilesCount: -1, + objStream: null, + haveOwnTimestamps: false, + isPausedTimeStamps: false, + strOwnIndent: "", + write: function( ...args: any[] ): void { + let s = ( this.strOwnIndent ? this.strOwnIndent : "" ) + + ( ( this.haveOwnTimestamps && ( !this.isPausedTimeStamps ) ) + ? generateTimestampPrefix( null, true ) + : "" ); + s += fmtArgumentsArray( args ); + try { + if( this.objStream && s.length > 0 ) + this.objStream.write( s ); + } catch ( err ) { } + }, + writeRaw: function( ...args: any[] ): void { + const s = fmtArgumentsArray( args ); + try { + if( this.objStream && s.length > 0 ) + this.objStream.write( s ); + } catch ( err ) { } + }, + close: function(): void { this.objStream = null; }, + open: function(): void { try { this.objStream = process.stdout; } catch ( err ) { } }, + size: function(): number { return 0; }, + rotate: function( nBytesToWrite: number ) { }, + toString: function(): string { return this.strPath.toString(); }, + exposeDetailsTo: + function( otherStream: TLogger, strTitle: string, isSuccess: boolean ): void { }, + // high-level formatters + fatal: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "fatal" ) ) + this.write( getLogLinePrefixFatal() + fmtFatal( ...args ) ); + }, + critical: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "critical" ) ) { + this.write( + getLogLinePrefixCritical() + fmtCritical( ...args ) ); + } + }, + error: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "error" ) ) + this.write( getLogLinePrefixError() + fmtError( ...args ) ); + }, + warning: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "warning" ) ) + this.write( getLogLinePrefixWarning() + fmtWarning( ...args ) ); + }, + attention: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "attention" ) ) { + this.write( + getLogLinePrefixAttention() + fmtAttention( ...args ) ); + } + }, + information: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) { + this.write( + getLogLinePrefixInformation() + fmtInformation( ...args ) ); + } + }, + info: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) { + this.write( + getLogLinePrefixInformation() + fmtInformation( ...args ) ); + } + }, + notice: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "notice" ) ) + this.write( getLogLinePrefixNotice() + fmtNotice( ...args ) ); + }, + note: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "notice" ) ) + this.write( getLogLinePrefixNote() + fmtNote( ...args ) ); + }, + debug: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "debug" ) ) + this.write( getLogLinePrefixDebug() + fmtDebug( ...args ) ); + }, + trace: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "trace" ) ) + this.write( getLogLinePrefixTrace() + fmtTrace( ...args ) ); + }, + success: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) + this.write( getLogLinePrefixSuccess() + fmtSuccess( ...args ) ); + } + }; + objEntry.open(); + return objEntry; + } catch ( err ) { + } + return null; +} + +export function insertStandardOutputStream(): boolean { + let objEntry = getStreamWithFilePath( "stdout" ); + if( objEntry !== null ) + return true; + objEntry = createStandardOutputStream(); + if( !objEntry ) + return false; + gArrStreams.push( objEntry ); + return true; +} + +export function createMemoryOutputStream(): TLogger { + try { + const objEntry: TLoggerMemory = { + id: gIdentifierAllocatorCounter++, + strPath: "memory", + nMaxSizeBeforeRotation: -1, + nMaxFilesCount: -1, + objStream: null, + arrAccumulatedLogTextLines: [], + haveOwnTimestamps: true, + isPausedTimeStamps: false, + strOwnIndent: " ", + isBeginningOfAccumulatedLog: function(): boolean { + if( this.arrAccumulatedLogTextLines.length == 0 ) + return true; + return false; + }, + isLastLineEndsWithCarriageReturn: function(): boolean { + if( this.arrAccumulatedLogTextLines.length == 0 ) + return false; + const s = this.arrAccumulatedLogTextLines[ + this.arrAccumulatedLogTextLines.length - 1]; + if( !s ) + return false; + if( s[s.length - 1] == "\n" ) + return true; + return false; + }, + write: function( ...args: any[] ): void { + const s = fmtArgumentsArray( args ); + const arr = s.split( "\n" ); + for( let i = 0; i < arr.length; ++i ) { + const strLine = arr[i]; + let strHeader = ""; + if( this.isLastLineEndsWithCarriageReturn() || + this.isBeginningOfAccumulatedLog() ) { + strHeader = ( this.strOwnIndent ? this.strOwnIndent : "" ); + if( this.haveOwnTimestamps && ( !this.isPausedTimeStamps ) ) + strHeader += generateTimestampPrefix( null, true ); + } + this.arrAccumulatedLogTextLines.push( strHeader + strLine + "\n" ); + } + }, + writeRaw: function( ...args: any[] ): void { + const s = fmtArgumentsArray( args ); + const arr = s.split( "\n" ); + for( let i = 0; i < arr.length; ++i ) { + const strLine = arr[i]; + this.arrAccumulatedLogTextLines.push( strLine + "\n" ); + } + }, + clear: function(): void { this.arrAccumulatedLogTextLines = []; }, + close: function(): void { this.clear(); }, + open: function(): void { this.clear(); }, + size: function(): number { return 0; }, + rotate: + function( nBytesToWrite: number ) { this.arrAccumulatedLogTextLines = []; }, + toString: function(): string { + let s = ""; + for( let i = 0; i < this.arrAccumulatedLogTextLines.length; ++i ) + s += this.arrAccumulatedLogTextLines[i]; + return s; + }, + exposeDetailsTo: + function( otherStream: TLogger, strTitle: string, isSuccess: boolean ): void { + if( !( this.arrAccumulatedLogTextLines && + this.arrAccumulatedLogTextLines.length > 0 ) ) + return; + let werePausedTimeStamps = false; + try { + werePausedTimeStamps = ( !!otherStream.isPausedTimeStamps ); + otherStream.isPausedTimeStamps = true; + } catch ( err ) { + } + try { + strTitle = strTitle + ? ( cc.bright( " (" ) + cc.attention( strTitle ) + cc.bright( ")" ) ) + : ""; + const strSuccessPrefix = isSuccess + ? cc.success( "SUCCESS" ) + : cc.error( "ERROR" ); + otherStream.write( "\n" ); + otherStream.write( cc.bright( "--- --- --- --- --- GATHERED " ) + + strSuccessPrefix + cc.bright( " DETAILS FOR LATEST(" ) + + cc.sunny( strTitle ) + cc.bright( " action (" ) + cc.sunny( "BEGIN" ) + + cc.bright( ") --- --- ------ --- " ) ); + otherStream.write( "\n" ); + for( let i = 0; i < this.arrAccumulatedLogTextLines.length; ++i ) { + try { + otherStream.writeRaw( this.arrAccumulatedLogTextLines[i] ); + } catch ( err ) { + } + } + otherStream.write( cc.bright( "--- --- --- --- --- GATHERED " ) + + strSuccessPrefix + cc.bright( " DETAILS FOR LATEST(" ) + + cc.sunny( strTitle ) + cc.bright( " action (" ) + cc.sunny( "END" ) + + cc.bright( ") --- --- --- --- ---" ) ); + otherStream.write( "\n" ); + } catch ( err ) { + } + try { + otherStream.isPausedTimeStamps = werePausedTimeStamps; + } catch ( err ) { + } + }, + // high-level formatters + fatal: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "fatal" ) ) + this.write( getLogLinePrefixFatal() + fmtFatal( ...args ) ); + }, + critical: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "critical" ) ) + this.write( getLogLinePrefixCritical() + fmtCritical( ...args ) ); + }, + error: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "error" ) ) + this.write( getLogLinePrefixError() + fmtError( ...args ) ); + }, + warning: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "warning" ) ) + this.write( getLogLinePrefixWarning() + fmtWarning( ...args ) ); + }, + attention: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "attention" ) ) { + this.write( + getLogLinePrefixAttention() + fmtAttention( ...args ) ); + } + }, + information: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) { + this.write( + getLogLinePrefixInformation() + fmtInformation( ...args ) ); + } + }, + info: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) { + this.write( + getLogLinePrefixInformation() + fmtInformation( ...args ) ); + } + }, + notice: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "notice" ) ) + this.write( getLogLinePrefixNotice() + fmtNotice( ...args ) ); + }, + note: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "notice" ) ) + this.write( getLogLinePrefixNote() + fmtNote( ...args ) ); + }, + debug: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "debug" ) ) + this.write( getLogLinePrefixDebug() + fmtDebug( ...args ) ); + }, + trace: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "trace" ) ) + this.write( getLogLinePrefixTrace() + fmtTrace( ...args ) ); + }, + success: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) + this.write( getLogLinePrefixSuccess() + fmtSuccess( ...args ) ); + } + }; + objEntry.open(); + return objEntry; + } catch ( err ) { + } + return globalStream(); +} + +export function insertMemoryOutputStream(): boolean { + let objEntry = getStreamWithFilePath( "memory" ); + if( objEntry !== null ) + return true; + objEntry = createMemoryOutputStream(); + if( !objEntry ) + return false; + gArrStreams.push( objEntry ); + return true; +} + +export function createFileOutput( + strFilePath: string, nMaxSizeBeforeRotation?: number, nMaxFilesCount?: number +): TLogger | null { + try { + const objEntry: TLogger = { + id: gIdentifierAllocatorCounter++, + strPath: strFilePath.toString(), + nMaxSizeBeforeRotation: cc.toInteger( nMaxSizeBeforeRotation ?? 0 ), + nMaxFilesCount: cc.toInteger( nMaxFilesCount ?? 0 ), + objStream: null, + haveOwnTimestamps: false, + isPausedTimeStamps: false, + strOwnIndent: "", + write: function( ...args: any[] ): void { + let s = ( this.strOwnIndent ? this.strOwnIndent : "" ) + + ( ( this.haveOwnTimestamps && ( !this.isPausedTimeStamps ) ) + ? generateTimestampPrefix( null, true ) + : "" ); + s += fmtArgumentsArray( args ); + try { + if( s.length > 0 ) { + this.rotate( s.length ); + fs.appendFileSync( this.objStream, s, "utf8" ); + } + } catch ( err ) { } + }, + writeRaw: function( ...args: any[] ): void { + const s = fmtArgumentsArray( args ); + try { + if( s.length > 0 ) { + this.rotate( s.length ); + fs.appendFileSync( this.objStream, s, "utf8" ); + } + } catch ( err ) { } + }, + close: function(): void { + if( !this.objStream ) + return; + fs.closeSync( this.objStream ); + this.objStream = null; + }, + open: function(): void { + this.objStream = + fs.openSync( this.strPath, "a", fs.constants.O_NONBLOCK | fs.constants.O_RDWR ); + }, + size: function(): number { + try { return fs.lstatSync( this.strPath ).size; } catch ( err ) { return 0; } + }, + rotate: function( nBytesToWrite: number ) { + try { + if( this.nMaxSizeBeforeRotation <= 0 || this.nMaxFilesCount <= 1 ) + return; + this.close(); + const nFileSize = this.size(); + const nNextSize = nFileSize + nBytesToWrite; + if( nNextSize <= this.nMaxSizeBeforeRotation ) { + this.open(); + return; + } + let i = 0; const cnt = cc.toInteger( this.nMaxFilesCount ); + for( i = 0; i < cnt; ++i ) { + const j = this.nMaxFilesCount - i - 1; + const strPath = + this.strPath.toString() + ( ( j === 0 ) ? "" : ( "." + j ) ); + if( j == ( cnt - 1 ) ) { + try { fs.unlinkSync( strPath ); } catch ( err ) { } + continue; + } + const strPathPrev = + this.strPath.toString() + "." + ( j + 1 ); + try { fs.unlinkSync( strPathPrev ); } catch ( err ) { } + try { fs.renameSync( strPath, strPathPrev ); } catch ( err ) { } + } + } catch ( err ) { + } + try { + this.open(); + } catch ( err ) { + } + }, + toString: function(): string { return strFilePath.toString(); }, + exposeDetailsTo: + function( otherStream: TLogger, strTitle: string, isSuccess: boolean ): void { }, + // high-level formatters + fatal: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "fatal" ) ) + this.write( getLogLinePrefixFatal() + fmtFatal( ...args ) ); + }, + critical: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "critical" ) ) { + this.write( + getLogLinePrefixCritical() + fmtCritical( ...args ) ); + } + }, + error: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "error" ) ) + this.write( getLogLinePrefixError() + fmtError( ...args ) ); + }, + warning: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "warning" ) ) + this.write( getLogLinePrefixWarning() + fmtWarning( ...args ) ); + }, + attention: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "attention" ) ) { + this.write( + getLogLinePrefixAttention() + fmtAttention( ...args ) ); + } + }, + information: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) { + this.write( + getLogLinePrefixInformation() + fmtInformation( ...args ) ); + } + }, + info: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) { + this.write( + getLogLinePrefixInformation() + fmtInformation( ...args ) ); + } + }, + notice: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "notice" ) ) + this.write( getLogLinePrefixNotice() + fmtNotice( ...args ) ); + }, + note: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "notice" ) ) + this.write( getLogLinePrefixNote() + fmtNote( ...args ) ); + }, + debug: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "debug" ) ) + this.write( getLogLinePrefixDebug() + fmtDebug( ...args ) ); + }, + trace: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "trace" ) ) + this.write( getLogLinePrefixTrace() + fmtTrace( ...args ) ); + }, + success: function( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) + this.write( getLogLinePrefixSuccess() + fmtSuccess( ...args ) ); + } + }; + objEntry.open(); + return objEntry; + } catch ( err ) { + console.log( + "CRITICAL ERROR: Failed to open file system log stream for " + strFilePath + + ", error is " + JSON.stringify( err ) + ); + } + return null; +} +export function insertFileOutput( + strFilePath: string, nMaxSizeBeforeRotation?: number, nMaxFilesCount?: number ): boolean { + let objEntry = getStreamWithFilePath( strFilePath.toString() ); + if( objEntry !== null ) + return true; + objEntry = createFileOutput( strFilePath, nMaxSizeBeforeRotation, nMaxFilesCount ); + if( !objEntry ) + return false; + gArrStreams.push( objEntry ); + return true; +} + +export function extractErrorMessage( jo?: any, strDefaultErrorText?: string ): string { + strDefaultErrorText = strDefaultErrorText ?? "unknown error or error without a description"; + if( !jo ) + return strDefaultErrorText; + try { + const isError = function( err: Error | string ): boolean { + return !!( ( err && err instanceof Error && err.stack && err.message ) ); + }; + if( !isError( jo ) ) { + if( "error" in jo ) { + jo = jo.error; + if( typeof jo === "string" ) + return jo; + if( typeof jo !== "object" ) + return strDefaultErrorText + "(" + jo.toString() + ")"; + } + if( typeof jo === "string" && jo ) + return strDefaultErrorText + "(" + jo.toString() + ")"; + return strDefaultErrorText; + } + if( typeof jo.message === "string" && jo.message.length > 0 ) + return jo.message; + strDefaultErrorText += "(" + jo.toString() + ")"; + } catch ( err ) { + } + return strDefaultErrorText; +} + +function tryToSplitFormatString( strFormat?: string, cntArgsMax?: number ): any[] | null { + if( !( strFormat && typeof strFormat === "string" ) ) + return null; + if( !cntArgsMax ) + cntArgsMax = 0; + const arrParts: any[] = []; + let s = strFormat; let cntFoundArgs = 0; + for( ; true; ) { + if( cntFoundArgs >= cntArgsMax ) + break; // nothing to do split for + const nStart = s.indexOf( "{" ); + if( nStart < 0 ) + break; + const nEnd = s.indexOf( "}", nStart + 1 ); + if( nEnd < 0 ) + break; + const strPart = s.substring( 0, nStart ); + const strArgDesc = s.substring( nStart + 1, nEnd ).trim().toLowerCase(); + s = s.substring( nEnd + 1 ); + if( strPart.length > 0 ) + arrParts.push( { type: "text", text: strPart } ); + arrParts.push( { type: "arg", text: strArgDesc } ); + ++cntFoundArgs; + if( s.length == 0 ) + break; + } + if( cntFoundArgs == 0 ) + return null; + if( s.length > 0 ) + arrParts.push( { type: "text", text: s } ); + return arrParts; +} + +export function fmtArgumentsArray( arrArgs: any[], fnFormatter?: any ): string { + fnFormatter = fnFormatter || function( arg: any ): any { return arg; }; + const arrParts = ( arrArgs && arrArgs.length > 0 ) + ? tryToSplitFormatString( arrArgs[0], arrArgs.length - 1 ) + : null; + let s = ""; let isValueMode = false; + const fnDefaultOneArgumentFormatter = function( arg?: any, fnCustomFormatter?: any ): string { + if( !fnCustomFormatter ) + fnCustomFormatter = fnFormatter; + const t = typeof arg; + if( t == "string" ) { + if( arg.length > 0 ) { + if( arg == " " || arg == "\n" ) { + // skip + } else if( !cc.isStringAlreadyColorized( arg ) ) + return fnCustomFormatter( arg ); + } + } else + return cc.logArgToString( arg ); + return arg; + }; + const fnFormatOneArgument = function( arg: any, fmt?: any ): string { + if( !arg ) + return arg; + if( arg == " " || arg == "\n" ) + return arg; + if( !isValueMode ) + return fnDefaultOneArgumentFormatter( arg, null ); + if( fmt && typeof "fmt" === "string" ) { + if( fmt == "raw" ) + return arg; + if( fmt == "p" ) + return fnDefaultOneArgumentFormatter( arg, null ); + if( fmt == "url" ) + return u( arg ); + if( fmt == "yn" ) + return yn( arg ); + if( fmt == "oo" ) + return onOff( arg ); + if( fmt == "stack" ) + return stack( arg ); + if( fmt == "em" ) + return em( arg ); + if( fmt == "err" ) + return em( extractErrorMessage( arg ) ); + if( fmt == "bright" ) + return fnDefaultOneArgumentFormatter( arg, cc.bright ); + if( fmt == "sunny" ) + return fnDefaultOneArgumentFormatter( arg, cc.sunny ); + if( fmt == "rainbow" ) + return fnDefaultOneArgumentFormatter( arg, cc.rainbow ); + } + return v( arg ); + }; + try { + let idxArgNextPrinted = 0; + if( arrParts && arrParts.length > 0 ) { + idxArgNextPrinted = 1; + for( let i = 0; i < arrParts.length; ++i ) { + const joPart = arrParts[i]; + if( joPart.type == "arg" ) { + isValueMode = true; + if( idxArgNextPrinted < arrArgs.length ) + s += fnFormatOneArgument( arrArgs[idxArgNextPrinted], joPart.text ); + ++idxArgNextPrinted; + continue; + } + // assume joPart.type == "text" always here, at this point + if( !cc.isStringAlreadyColorized( joPart.text ) ) + s += fnFormatter( joPart.text ); + else + s += joPart.text; + } + } + for( let i = idxArgNextPrinted; i < arrArgs.length; ++i ) { + try { + s += fnFormatOneArgument( arrArgs[i], null ); + } catch ( err ) { + } + } + } catch ( err ) { + } + return s; +} + +export function outputStringToAllStreams( s: string ): void { + try { + if( s.length <= 0 ) + return; + for( let i = 0; i < gArrStreams.length; ++i ) { + try { + const objEntry = gArrStreams[i]; + if( objEntry && "write" in objEntry && typeof objEntry.write === "function" ) + objEntry.write( s ); + } catch ( err ) { + } + } + } catch ( err ) { + } +} + +export function write( ...args: any[] ): void { + let s: string = getPrintTimestamps() ? generateTimestampPrefix( null, true ) : ""; + s += fmtArgumentsArray( args ); + outputStringToAllStreams( s ); +} +export function writeRaw( ...args: any[] ): void { + const s: string = fmtArgumentsArray( args ); + outputStringToAllStreams( s ); +} + +export function getLogLinePrefixFatal(): string { + return cc.fatal( "FATAL ERROR:" ) + " "; +} +export function getLogLinePrefixCritical(): string { + return cc.fatal( "CRITICAL ERROR:" ) + " "; +} +export function getLogLinePrefixError(): string { + return cc.fatal( "ERROR:" ) + " "; +} +export function getLogLinePrefixWarning(): string { + return cc.error( "WARNING:" ) + " "; +} +export function getLogLinePrefixAttention(): string { + return ""; +} +export function getLogLinePrefixInformation(): string { + return ""; +} +export function getLogLinePrefixNotice(): string { + return ""; +} +export function getLogLinePrefixNote(): string { + return ""; +} +export function getLogLinePrefixDebug(): string { + return ""; +} +export function getLogLinePrefixTrace(): string { + return ""; +} +export function getLogLinePrefixSuccess(): string { + return ""; +} + +// high-level format to returned string +export function fmtFatal( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.error ); +} +export function fmtCritical( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.error ); +} +export function fmtError( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.error ); +} +export function fmtWarning( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.warning ); +} +export function fmtAttention( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.attention ); +} +export function fmtInformation( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.info ); +} +export function fmtInfo( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.info ); +} +export function fmtNotice( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.notice ); +} +export function fmtNote( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.note ); +} +export function fmtDebug( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.debug ); +} +export function fmtTrace( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.trace ); +} +export function fmtSuccess( ...args: any[] ): string { + return fmtArgumentsArray( args, cc.success ); +} + +// high-level formatted output +export function fatal( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "fatal" ) ) + write( getLogLinePrefixFatal() + fmtFatal( ...args ) + "\n" ); +} +export function critical( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "critical" ) ) + write( getLogLinePrefixCritical() + fmtCritical( ...args ) + "\n" ); +} +export function error( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "error" ) ) + write( getLogLinePrefixError() + fmtError( ...args ) + "\n" ); +} +export function warning( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "warning" ) ) + write( getLogLinePrefixWarning() + fmtWarning( ...args ) + "\n" ); +} +export function attention( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "attention" ) ) + write( getLogLinePrefixAttention() + fmtAttention( ...args ) + "\n" ); +} +export function information( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) + write( getLogLinePrefixInformation() + fmtInformation( ...args ) + "\n" ); +} +export function info( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) + write( getLogLinePrefixInformation() + fmtInformation( ...args ) + "\n" ); +} +export function notice( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "notice" ) ) + write( getLogLinePrefixNotice() + fmtNotice( ...args ) + "\n" ); +} +export function note( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "notice" ) ) + write( getLogLinePrefixNote() + fmtNote( ...args ) + "\n" ); +} +export function debug( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "debug" ) ) + write( getLogLinePrefixDebug() + fmtDebug( ...args ) + "\n" ); +} +export function trace( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "trace" ) ) + write( getLogLinePrefixTrace() + fmtTrace( ...args ) + "\n" ); +} +export function success( ...args: any[] ): void { + if( verboseGet() >= verboseName2Number( "information" ) ) + write( getLogLinePrefixSuccess() + fmtSuccess( ...args ) + "\n" ); +} + +export function removeAll(): void { + removeAllStreams(); +} + +export function addStdout(): boolean { + return insertStandardOutputStream(); +} + +export function addMemory(): boolean { + return insertMemoryOutputStream(); +} + +export function createMemoryStream(): TLogger { + return createMemoryOutputStream(); +} + +export function add( + strFilePath: string, nMaxSizeBeforeRotation?: number, nMaxFilesCount?: number ): boolean { + if( !nMaxSizeBeforeRotation ) + nMaxSizeBeforeRotation = 0; + if( !nMaxFilesCount ) + nMaxFilesCount = 0; + return insertFileOutput( + strFilePath, + ( nMaxSizeBeforeRotation <= 0 ) ? -1 : nMaxSizeBeforeRotation, + ( nMaxFilesCount <= 1 ) ? -1 : nMaxFilesCount + ); +} + +export function close(): void { + // for compatibility with created streams +} + +export function exposeDetailsTo( + otherStream: TLogger, strTitle: string, isSuccess: boolean ): void { + // for compatibility with created streams +} + +export function toString(): string { + // for compatibility with created streams + return ""; +} + +const gMapVerbose: Map < number, string > = new Map < number, string >(); +gMapVerbose.set( 0, "silent" ); +gMapVerbose.set( 1, "fatal" ); +gMapVerbose.set( 2, "critical" ); +gMapVerbose.set( 3, "error" ); +gMapVerbose.set( 4, "warning" ); +gMapVerbose.set( 5, "attention" ); +gMapVerbose.set( 6, "information" ); +gMapVerbose.set( 7, "notice" ); +gMapVerbose.set( 8, "debug" ); +gMapVerbose.set( 9, "trace" ); + +function computeVerboseAlias(): Map < string, number > { + const m: Map < string, number > = new Map < string, number >(); + for( const [ key, val ] of gMapVerbose ) { + const name = val; + if( name ) + m.set( name, key ); + } + m.set( "empty", m.get( "silent" ) ?? 0 ); // alias + m.set( "none", m.get( "silent" ) ?? 0 ); // alias + m.set( "stop", m.get( "fatal" ) ?? 0 ); // alias + m.set( "bad", m.get( "critical" ) ?? 0 ); // alias + m.set( "err", m.get( "error" ) ?? 0 ); // alias + m.set( "warn", m.get( "warning" ) ?? 0 ); // alias + m.set( "attn", m.get( "attention" ) ?? 0 ); // alias + m.set( "info", m.get( "information" ) ?? 0 ); // alias + m.set( "note", m.get( "notice" ) ?? 0 ); // alias + m.set( "dbg", m.get( "debug" ) ?? 0 ); // alias + m.set( "crazy", m.get( "trace" ) ?? 0 ); // alias + m.set( "detailed", m.get( "trace" ) ?? 0 ); // alias + return m; +} +let gMapReversedVerbose: Map < string, number > = new Map < string, number >(); + +export function verbose(): any { return gMapVerbose; } +export function verboseReversed(): Map < string, number > { + if( !gMapReversedVerbose ) + gMapReversedVerbose = computeVerboseAlias(); + return gMapReversedVerbose; +} +export function verboseLevelAsTextForLog( vl: any ): string { + if( typeof vl === "undefined" ) + vl = verboseGet(); + if( vl in gMapVerbose ) { + const tl = gMapVerbose.get( vl ) ?? 0; + return tl.toString(); + } + return "unknown(" + JSON.stringify( vl ) + ")"; +} +export function verboseName2Number( s: string ): number { + const mapReversedVerbose: Map < string, number > = verboseReversed(); + const n = mapReversedVerbose.get( s ); + if( typeof n === "undefined" ) + return 9; + return n; +} + +let gFlagIsExposeDetails = false; +let gVerboseLevel = verboseName2Number( "information" ); + +export function exposeDetailsGet(): boolean { + return ( !!gFlagIsExposeDetails ); +} +export function exposeDetailsSet( isExpose: any ): void { + gFlagIsExposeDetails = ( !!isExpose ); +} + +export function verboseGet(): number { + return cc.toInteger( gVerboseLevel ); +} +export function verboseSet( vl?: any ): void { + gVerboseLevel = parseInt( vl ); +} + +export function verboseParse( s: string ): number { + let n: number = 5; + try { + const isNumbersOnly = /^\d+$/.test( s ); + if( isNumbersOnly ) + n = cc.toInteger( s ); + else { + const ch0 = s[0].toLowerCase(); + for( const [ key, val ] of gMapVerbose ) { + const name = val; + const ch1: string = name[0].toLowerCase(); + if( ch0 == ch1 ) { + n = key; + return n; + } + } + } + } catch ( err ) { } + return n; +} + +export function verboseList(): void { + for( const [ key, val ] of gMapVerbose ) { + const name = val; + console.log( " " + cc.j( key ) + cc.sunny( "=" ) + cc.bright( name ) ); + } +} + +export function u( x?: any ): string { + return cc.isStringAlreadyColorized( x ) ? x : cc.u( x ); +} + +export function v( x?: any ): string { + return cc.isStringAlreadyColorized( x ) ? x : cc.j( x ); +} + +export function em( x?: any ): string { + return cc.isStringAlreadyColorized( x ) ? x : cc.warning( x ); +} + +export function stack( err?: any ): string { + return cc.stack( err ); +} + +export function onOff( x?: any ): string { + return cc.isStringAlreadyColorized( x ) ? x : cc.onOff( x ); +} + +export function yn( x?: any ): string { + return cc.isStringAlreadyColorized( x ) ? x : cc.yn( x ); +} + +export function posNeg( condition: any, strPositive: string, strNegative: string ): string { + return condition + ? ( cc.isStringAlreadyColorized( strPositive ) ? strPositive : cc.success( strPositive ) ) + : ( cc.isStringAlreadyColorized( strNegative ) ? strNegative : cc.error( strNegative ) ); +} diff --git a/src/loop.ts b/src/loop.ts new file mode 100644 index 00000000..6e5ffcd3 --- /dev/null +++ b/src/loop.ts @@ -0,0 +1,798 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file loop.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as networkLayer from "./socket.js"; +import * as url from "url"; +import * as threadInfo from "./threadInfo.js"; +import * as path from "path"; +import * as log from "./log.js"; +import * as IMA from "./imaCore.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaTransferErrorHandling from "./imaTransferErrorHandling.js"; +import * as imaOracleOperations from "./imaOracleOperations.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as imaBLS from "./bls.js"; +import * as skaleObserver from "./observer.js"; +import * as pwa from "./pwa.js"; +import * as state from "./state.js"; +import type * as worker_threads from "worker_threads"; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const __dirname: string = path.dirname( url.fileURLToPath( import.meta.url ) ); + +export interface TExtraSignOpts { + chainNameSrc: string + chainIdSrc: string + chainNameDst: string + chainIdDst: string + joAccountSrc?: state.TAccount + joAccountDst?: state.TAccount + ethersProviderSrc?: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider + ethersProviderDst?: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider +} + +export interface TRuntimeOpts { + isInsideWorker: boolean + idxChainKnownForS2S: number + cntChainsKnownForS2S: number + joExtraSignOpts?: TExtraSignOpts +} + +export interface TLoopOptions { + joRuntimeOpts: TRuntimeOpts + isDelayFirstRun: boolean + enableStepOracle: boolean + enableStepM2S: boolean + enableStepS2M: boolean + enableStepS2S: boolean +} + +export interface TParallelLoopRunOptions { + imaState: state.TIMAState + details: log.TLogger +} + +// Run transfer loop + +export function checkTimeFraming( + aDateTime: Date | null, strDirection: string, joRuntimeOpts: TRuntimeOpts ): boolean { + try { + const imaState: state.TIMAState = state.get(); + if( imaState.nTimeFrameSeconds <= 0 || imaState.nNodesCount <= 1 ) + return true; // time framing is disabled + + if( aDateTime == null || aDateTime == undefined ) + aDateTime = new Date(); // now + + const nFrameShift = 0; + + // Unix UTC timestamp, see: + // https://stackoverflow.com/questions/9756120/how-do-i-get-a-utc-timestamp-in-javascript + const nUtcUnixTimeStamp = Math.floor( ( aDateTime ).getTime() / 1000 ); + + const nSecondsRangeForAllSChains = imaState.nTimeFrameSeconds * imaState.nNodesCount; + const nMod = Math.floor( nUtcUnixTimeStamp % nSecondsRangeForAllSChains ); + let nActiveNodeFrameIndex = Math.floor( nMod / imaState.nTimeFrameSeconds ); + if( nFrameShift > 0 ) { + nActiveNodeFrameIndex += nFrameShift; + nActiveNodeFrameIndex %= imaState.nNodesCount; // for safety only + } + let bSkip = ( nActiveNodeFrameIndex != imaState.nNodeNumber ); + let bInsideGap = false; + + const nRangeStart = + nUtcUnixTimeStamp - + Math.floor( nUtcUnixTimeStamp % nSecondsRangeForAllSChains ); + const nFrameStart = nRangeStart + imaState.nNodeNumber * imaState.nTimeFrameSeconds; + const nGapStart = nFrameStart + imaState.nTimeFrameSeconds - imaState.nNextFrameGap; + if( !bSkip ) { + if( nUtcUnixTimeStamp >= nGapStart ) { + bSkip = true; + bInsideGap = true; + } + } + let strFrameInfo = log.fmtDebug( "\n", + " Unix UTC time stamp", "........", + log.fmtInformation( "{}", nUtcUnixTimeStamp ), "\n", + " All Chains Range", "...........", nSecondsRangeForAllSChains, "\n", + " S-Chain Range Mod", "..........", log.fmtInformation( "{}", nMod ), "\n", + " Active Node Frame Index", "....", + log.fmtInformation( "{}", nActiveNodeFrameIndex ), "\n", + " Testing Frame Index", "........", + log.fmtInformation( "{}", imaState.nNodeNumber ), "\n", + " Transfer Direction", ".........", + log.fmtInformation( "{bright}", strDirection || "NA" ), "\n" ); + if( nFrameShift > 0 ) { + strFrameInfo += log.fmtDebug( + " Frame Shift", "................", + log.fmtInformation( "{}", nFrameShift ), "\n", + " S2S known chain index", "......", + log.fmtInformation( "{}", joRuntimeOpts.idxChainKnownForS2S ), "\n", + " S2S known chains count", ".....", + log.fmtInformation( "{}", joRuntimeOpts.cntChainsKnownForS2S ), "\n" + ); + if( "joExtraSignOpts" in joRuntimeOpts && + typeof joRuntimeOpts.joExtraSignOpts === "object" ) { + strFrameInfo += log.fmtDebug( " S-Chain source", ".............", + log.fmtInformation( "{}", joRuntimeOpts.joExtraSignOpts.chainNameSrc ), + "/", log.fmtInformation( "{}", joRuntimeOpts.joExtraSignOpts.chainIdSrc ), + "\n" ); + } else { + const s1: string = log.fmtInformation( "{}", + joRuntimeOpts.joExtraSignOpts + ? joRuntimeOpts.joExtraSignOpts.chainNameDst + : "N/A" ); + const s2: string = log.fmtInformation( "{}", + joRuntimeOpts.joExtraSignOpts + ? joRuntimeOpts.joExtraSignOpts.chainIdDst + : "N/A" ); + strFrameInfo += log.fmtDebug( " S-Chain destination", "........", + s1, "/", s2, "\n" ); + } + } + strFrameInfo += log.fmtDebug( + " Is skip", "....................", log.yn( bSkip ), "\n", + " Is inside gap", "..............", log.yn( bInsideGap ), "\n", + " Range Start", "................", log.fmtInformation( "{}", nRangeStart ), "\n", + " Frame Start", "................", log.fmtInformation( "{}", nFrameStart ), "\n", + " Gap Start", "..................", log.fmtInformation( "{}", nGapStart ), "\n" ); + log.write( strFrameInfo ); + if( bSkip ) + return false; + } catch ( err ) { + log.error( "Exception in time framing check in {}: {err}, stack is:{}{stack}", + threadInfo.threadDescription(), err, "\n", err ); + } + return true; +}; + +async function singleTransferLoopPartOracle( + optsLoop: TLoopOptions, strLogPrefix: string ): Promise { + const imaState: state.TIMAState = state.get(); + let b0 = true; + if( optsLoop.enableStepOracle && imaOracleOperations.getEnabledOracle() ) { + log.notice( "{p}Will invoke Oracle gas price setup in {}...", + strLogPrefix, threadInfo.threadDescription() ); + try { + if( !await pwa.checkOnLoopStart( imaState, "oracle" ) ) { + imaState.loopState.oracle.wasInProgress = false; + log.notice( "{p}Skipped(oracle) in {} due to cancel mode reported from PWA", + strLogPrefix, threadInfo.threadDescription() ); + } else { + if( checkTimeFraming( null, "oracle", optsLoop.joRuntimeOpts ) ) { + imaState.loopState.oracle.isInProgress = true; + await pwa.notifyOnLoopStart( imaState, "oracle" ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joCommunityLocker ) + throw new Error( "No CommunityLocker contract" ); + b0 = await imaOracleOperations.doOracleGasPriceSetup( + imaState.chainProperties.mn.ethersProvider, + imaState.chainProperties.sc.ethersProvider, + imaState.chainProperties.sc.transactionCustomizer, + imaState.joCommunityLocker, + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.mn.chainId + ? imaState.chainProperties.mn.chainId.toString() + : "", + imaState.chainProperties.sc.chainId + ? imaState.chainProperties.sc.chainId.toString() + : "", + imaBLS.doSignU256 + ); + imaState.loopState.oracle.isInProgress = false; + await pwa.notifyOnLoopEnd( imaState, "oracle" ); + } else { + log.notice( "{p}Skipped(oracle) in {} due to time framing check", + strLogPrefix, threadInfo.threadDescription() ); + } + } + } catch ( err ) { + log.error( "{p}Oracle operation exception: {} in {err}, stack is:{}{stack}", + strLogPrefix, err, threadInfo.threadDescription(), "\n", err ); + imaState.loopState.oracle.isInProgress = false; + await pwa.notifyOnLoopEnd( imaState, "oracle" ); + throw err; + } + log.information( "{p}Oracle gas price setup done in {}: {}", + strLogPrefix, threadInfo.threadDescription(), b0 ); + } + return b0; +} + +async function singleTransferLoopPartM2S( + optsLoop: TLoopOptions, strLogPrefix: string ): Promise { + const imaState: state.TIMAState = state.get(); + let b1 = true; + if( optsLoop.enableStepM2S ) { + log.notice( "{p}Will invoke M2S transfer in {}...", + strLogPrefix, threadInfo.threadDescription() ); + try { + if( !await pwa.checkOnLoopStart( imaState, "m2s" ) ) { + imaState.loopState.m2s.wasInProgress = false; + log.notice( "{p}Skipped(m2s) in {} due to cancel mode reported from PWA", + strLogPrefix, threadInfo.threadDescription() ); + } else { + if( checkTimeFraming( null, "m2s", optsLoop.joRuntimeOpts ) ) { + imaState.loopState.m2s.isInProgress = true; + await pwa.notifyOnLoopStart( imaState, "m2s" ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joMessageProxyMainNet ) + throw new Error( "No MessageProxyMainNet contract" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain ) contract" ); + b1 = await IMA.doTransfer( // main-net --> s-chain + "M2S", + optsLoop.joRuntimeOpts, + imaState.chainProperties.mn.ethersProvider, + imaState.joMessageProxyMainNet, + imaState.chainProperties.mn.joAccount, + imaState.chainProperties.sc.ethersProvider, + imaState.joMessageProxySChain, + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.mn.strChainName, + imaState.chainProperties.sc.strChainName, + imaState.chainProperties.mn.chainId + ? imaState.chainProperties.mn.chainId.toString() + : "", + imaState.chainProperties.sc.chainId + ? imaState.chainProperties.sc.chainId.toString() + : "", + null, + imaState.joTokenManagerETH, // for logs validation on s-chain + imaState.nTransferBlockSizeM2S, + imaState.nTransferStepsM2S, + imaState.nMaxTransactionsM2S, + imaState.nBlockAwaitDepthM2S, + imaState.nBlockAgeM2S, + imaBLS.doSignMessagesM2S, + null, + imaState.chainProperties.sc.transactionCustomizer + ); + imaState.loopState.m2s.isInProgress = false; + await pwa.notifyOnLoopEnd( imaState, "m2s" ); + } else { + log.notice( "{p}Skipped(m2s) in {} due to time framing check", + strLogPrefix, threadInfo.threadDescription() ); + } + } + } catch ( err ) { + log.error( "{p}M2S transfer exception in {}: {err}, stack is:{}{stack}", + strLogPrefix, threadInfo.threadDescription(), err, "\n", err ); + imaState.loopState.m2s.isInProgress = false; + await pwa.notifyOnLoopEnd( imaState, "m2s" ); + throw err; + } + log.information( "{p}M2S transfer done in {}: {}", + strLogPrefix, threadInfo.threadDescription(), b1 ); + } else + log.debug( "{p}Skipped M2S transfer in {}.", strLogPrefix, threadInfo.threadDescription() ); + return b1; +} + +async function singleTransferLoopPartS2M( + optsLoop: TLoopOptions, strLogPrefix: string ): Promise { + const imaState: state.TIMAState = state.get(); + let b2 = true; + if( optsLoop.enableStepS2M ) { + log.notice( "{p}Will invoke S2M transfer in {}...", + strLogPrefix, threadInfo.threadDescription() ); + try { + if( !await pwa.checkOnLoopStart( imaState, "s2m" ) ) { + imaState.loopState.s2m.wasInProgress = false; + log.notice( "{p}Skipped(s2m) in {} due to cancel mode reported from PWA", + strLogPrefix, threadInfo.threadDescription() ); + } else { + if( checkTimeFraming( null, "s2m", optsLoop.joRuntimeOpts ) ) { + imaState.loopState.s2m.isInProgress = true; + await pwa.notifyOnLoopStart( imaState, "s2m" ); + if( !imaState.chainProperties.mn.ethersProvider ) + throw new Error( "No provider for MN" ); + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joMessageProxyMainNet ) + throw new Error( "No MessageProxyMainNet contract" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain contract" ); + b2 = await IMA.doTransfer( // s-chain --> main-net + "S2M", + optsLoop.joRuntimeOpts, + + imaState.chainProperties.sc.ethersProvider, + imaState.joMessageProxySChain, + imaState.chainProperties.sc.joAccount, + + imaState.chainProperties.mn.ethersProvider, + imaState.joMessageProxyMainNet, + imaState.chainProperties.mn.joAccount, + + imaState.chainProperties.sc.strChainName, + imaState.chainProperties.mn.strChainName, + imaState.chainProperties.sc.chainId + ? imaState.chainProperties.sc.chainId.toString() + : "", + imaState.chainProperties.mn.chainId + ? imaState.chainProperties.mn.chainId.toString() + : "", + + imaState.joDepositBoxETH, // for logs validation on mainnet + null, + imaState.nTransferBlockSizeS2M, + imaState.nTransferStepsS2M, + imaState.nMaxTransactionsS2M, + imaState.nBlockAwaitDepthS2M, + imaState.nBlockAgeS2M, + imaBLS.doSignMessagesS2M, + null, + imaState.chainProperties.mn.transactionCustomizer + ); + imaState.loopState.s2m.isInProgress = false; + await pwa.notifyOnLoopEnd( imaState, "s2m" ); + } else { + log.notice( "{p}Skipped(s2m) in {} due to time framing check", + strLogPrefix, threadInfo.threadDescription() ); + } + } + } catch ( err ) { + log.error( "{p}S2M transfer exception in {err}: , stack is:{}{stack}", + strLogPrefix, threadInfo.threadDescription(), err, "\n", err ); + imaState.loopState.s2m.isInProgress = false; + await pwa.notifyOnLoopEnd( imaState, "s2m" ); + throw err; + } + log.information( "{p}S2M transfer done in {}: {}", + strLogPrefix, threadInfo.threadDescription(), b2 ); + } else { + log.debug( "{p}Skipped S2M transfer in {}.", + strLogPrefix, threadInfo.threadDescription() ); + } + return b2; +} + +async function singleTransferLoopPartS2S( + optsLoop: TLoopOptions, strLogPrefix: string ): Promise { + const imaState: state.TIMAState = state.get(); + let b3 = true; + if( optsLoop.enableStepS2S && imaState.optsS2S.isEnabled ) { + log.notice( "{p}Will invoke all S2S transfers...", strLogPrefix ); + try { + if( !imaState.chainProperties.sc.ethersProvider ) + throw new Error( "No provider for SC" ); + if( !imaState.joMessageProxySChain ) + throw new Error( "No MessageProxySChain contract" ); + if( !imaState.joTokenManagerETH ) + throw new Error( "No TokenManagerETH contract" ); + b3 = await IMA.doAllS2S( // s-chain --> s-chain + optsLoop.joRuntimeOpts, + imaState, + skaleObserver, + imaState.chainProperties.sc.ethersProvider, + imaState.joMessageProxySChain, + imaState.chainProperties.sc.joAccount, + imaState.chainProperties.sc.strChainName, + imaState.chainProperties.sc.chainId + ? imaState.chainProperties.sc.chainId.toString() + : "", + imaState.joTokenManagerETH, // for logs validation on s-chain + imaState.nTransferBlockSizeS2S, + imaState.nTransferStepsS2S, + imaState.nMaxTransactionsS2S, + imaState.nBlockAwaitDepthS2S, + imaState.nBlockAgeS2S, + imaBLS.doSignMessagesS2S, + imaState.chainProperties.sc.transactionCustomizer + ); + } catch ( err ) { + log.error( "{p}S2S transfer exception in {}: {err}, stack is:{}{stack}", + strLogPrefix, threadInfo.threadDescription(), err, "\n", err ); + throw err; + } + log.information( "{p}All S2S transfers done in {}: {}", + strLogPrefix, threadInfo.threadDescription(), b3 ); + } else + log.debug( "{p}Skipped S2S transfer in {}.", strLogPrefix, threadInfo.threadDescription() ); + + return b3; +} + +function printLoopPartSkippedWarning( strLoopPartName: string ): void { + log.warning( "Skipped {} transfer loop part due to other single transfer loop is in " + + "progress right now", strLoopPartName ); +} + +export async function singleTransferLoop( + optsLoop: TLoopOptions ): Promise { + const imaState: state.TIMAState = state.get(); + const strLogPrefix = `Single Loop in ${threadInfo.threadDescription( false )} `; + try { + log.debug( "{p}{p}", strLogPrefix, imaHelperAPIs.longSeparator ); + let b0 = false; let b1 = false; let b2 = false; let b3 = false; + // Oracle loop part: + if( optsLoop.enableStepOracle ) { + if( imaState.loopState.oracle.isInProgress ) { + imaState.loopState.oracle.wasInProgress = false; + printLoopPartSkippedWarning( "Oracle" ); + b0 = true; + } else + b0 = await singleTransferLoopPartOracle( optsLoop, strLogPrefix ); + } else + b0 = true; + // M2S loop part: + if( optsLoop.enableStepM2S ) { + if( imaState.loopState.m2s.isInProgress ) { + imaState.loopState.m2s.wasInProgress = false; + printLoopPartSkippedWarning( "M2S" ); + b1 = true; + } else + b1 = await singleTransferLoopPartM2S( optsLoop, strLogPrefix ); + } else + b1 = true; + // S2M loop part: + if( optsLoop.enableStepS2M ) { + if( imaState.loopState.s2m.isInProgress ) { + imaState.loopState.s2m.wasInProgress = false; + printLoopPartSkippedWarning( "S2M" ); + b2 = true; + } else + b2 = await singleTransferLoopPartS2M( optsLoop, strLogPrefix ); + } else + b2 = true; + // S2S loop part: + if( optsLoop.enableStepS2S ) { + if( imaState.loopState.s2s.isInProgress ) { + imaState.loopState.s2s.wasInProgress = false; + printLoopPartSkippedWarning( "S2S" ); + b3 = true; + } else + b3 = await singleTransferLoopPartS2S( optsLoop, strLogPrefix ); + } else + b3 = true; + // Final status check loop part: + const bResult = b0 && b1 && b2 && b3; + log.notice( "{p}Final completion status for all performed transfer loop parts is {}", + strLogPrefix, bResult ); + return bResult; + } catch ( err ) { + log.error( "{p}Exception in transfer loop: {err}, stack is:{}{stack}", strLogPrefix, + err, "\n", err ); + } + imaState.loopState.oracle.isInProgress = false; + imaState.loopState.m2s.isInProgress = false; + imaState.loopState.s2m.isInProgress = false; + imaState.loopState.s2s.isInProgress = false; + return false; +} +export async function singleTransferLoopWithRepeat( + optsLoop: TLoopOptions ): Promise { + const imaState: state.TIMAState = state.get(); + await singleTransferLoop( optsLoop ); + setTimeout( function(): void { + singleTransferLoopWithRepeat( optsLoop ) + .then( function(): void {} ).catch( function(): void {} ); + }, imaState.nLoopPeriodSeconds * 1000 ); +}; +export async function runTransferLoop( optsLoop: TLoopOptions ): Promise { + const imaState: state.TIMAState = state.get(); + const isDelayFirstRun = owaspUtils.toBoolean( optsLoop.isDelayFirstRun ); + if( isDelayFirstRun ) { + setTimeout( function(): void { + singleTransferLoopWithRepeat( optsLoop ) + .then( function(): void {} ).catch( function(): void {} ); + }, imaState.nLoopPeriodSeconds * 1000 ); + } else + await singleTransferLoopWithRepeat( optsLoop ); + return true; +} + +// Parallel thread based loop + +const gArrWorkers: worker_threads.Worker[] = []; +const gArrClients: networkLayer.OutOfWorkerSocketClientPipe[] = []; + +function constructChainProperties( opts: TParallelLoopRunOptions ): any { + return { + mn: { + joAccount: { + privateKey: opts.imaState.chainProperties.mn.joAccount.privateKey, + address_: opts.imaState.chainProperties.mn.joAccount.address_, + strTransactionManagerURL: + opts.imaState.chainProperties.mn.joAccount.strTransactionManagerURL, + nTmPriority: opts.imaState.chainProperties.mn.joAccount.nTmPriority, + strSgxURL: opts.imaState.chainProperties.mn.joAccount.strSgxURL, + strSgxKeyName: opts.imaState.chainProperties.mn.joAccount.strSgxKeyName, + strPathSslKey: opts.imaState.chainProperties.mn.joAccount.strPathSslKey, + strPathSslCert: opts.imaState.chainProperties.mn.joAccount.strPathSslCert, + strBlsKeyName: opts.imaState.chainProperties.mn.joAccount.strBlsKeyName + }, + ethersProvider: null, + strURL: opts.imaState.chainProperties.mn.strURL, + strChainName: opts.imaState.chainProperties.mn.strChainName, + chainId: opts.imaState.chainProperties.mn.chainId, + joAbiIMA: opts.imaState.chainProperties.mn.joAbiIMA, + bHaveAbiIMA: opts.imaState.chainProperties.mn.bHaveAbiIMA + }, + sc: { + joAccount: { + privateKey: opts.imaState.chainProperties.sc.joAccount.privateKey, + address_: opts.imaState.chainProperties.sc.joAccount.address_, + strTransactionManagerURL: + opts.imaState.chainProperties.sc.joAccount.strTransactionManagerURL, + nTmPriority: opts.imaState.chainProperties.sc.joAccount.nTmPriority, + strSgxURL: opts.imaState.chainProperties.sc.joAccount.strSgxURL, + strSgxKeyName: opts.imaState.chainProperties.sc.joAccount.strSgxKeyName, + strPathSslKey: opts.imaState.chainProperties.sc.joAccount.strPathSslKey, + strPathSslCert: opts.imaState.chainProperties.mn.joAccount.strPathSslCert, + strBlsKeyName: opts.imaState.chainProperties.mn.joAccount.strBlsKeyName + }, + ethersProvider: null, + strURL: opts.imaState.chainProperties.sc.strURL, + strChainName: opts.imaState.chainProperties.sc.strChainName, + chainId: opts.imaState.chainProperties.sc.chainId, + joAbiIMA: opts.imaState.chainProperties.sc.joAbiIMA, + bHaveAbiIMA: opts.imaState.chainProperties.sc.bHaveAbiIMA + }, + tc: { + joAccount: { + privateKey: opts.imaState.chainProperties.tc.joAccount.privateKey, + address_: opts.imaState.chainProperties.tc.joAccount.address_, + strTransactionManagerURL: + opts.imaState.chainProperties.tc.joAccount.strTransactionManagerURL, + nTmPriority: opts.imaState.chainProperties.tc.joAccount.nTmPriority, + strSgxURL: opts.imaState.chainProperties.tc.joAccount.strSgxURL, + strSgxKeyName: opts.imaState.chainProperties.tc.joAccount.strSgxKeyName, + strPathSslKey: opts.imaState.chainProperties.tc.joAccount.strPathSslKey, + strPathSslCert: opts.imaState.chainProperties.tc.joAccount.strPathSslCert, + strBlsKeyName: opts.imaState.chainProperties.tc.joAccount.strBlsKeyName + }, + ethersProvider: null, + strURL: opts.imaState.chainProperties.tc.strURL, + strChainName: opts.imaState.chainProperties.tc.strChainName, + chainId: opts.imaState.chainProperties.tc.chainId, + joAbiIMA: opts.imaState.chainProperties.tc.joAbiIMA, + bHaveAbiIMA: opts.imaState.chainProperties.tc.bHaveAbiIMA + } + }; +} + +function getDefaultOptsLoop( idxWorker: number ): TLoopOptions { + const optsLoop: TLoopOptions = { + joRuntimeOpts: { + isInsideWorker: true, idxChainKnownForS2S: 0, cntChainsKnownForS2S: 0 + }, + isDelayFirstRun: false, + enableStepOracle: ( idxWorker == 0 ), + enableStepM2S: ( idxWorker == 0 ), + enableStepS2M: ( idxWorker == 1 ), + enableStepS2S: ( idxWorker == 0 ) + }; + return optsLoop; +} + +interface TWorkerData { + url: string + colorization: { + isEnabled: boolean + } +} + +export async function ensureHaveWorkers( opts: TParallelLoopRunOptions ): Promise { + if( gArrWorkers.length > 0 ) + return gArrWorkers; + const cntWorkers = 2; + log.debug( "Loop module will create its ", + cntWorkers, " worker(s) in ", threadInfo.threadDescription(), "..." ); + for( let idxWorker = 0; idxWorker < cntWorkers; ++idxWorker ) { + const workerData: TWorkerData = { + url: "ima_loop_server" + idxWorker, + colorization: { isEnabled: log.isEnabledColorization() } + }; + gArrWorkers.push( new threadInfo.Worker( + path.join( __dirname, "loopWorker.js" ), + { // "type": "module", + workerData + } + ) ); + gArrWorkers[idxWorker].on( "message", function( jo: any ): void { + networkLayer.outOfWorkerAPIs.onMessage( gArrWorkers[idxWorker], jo ); + } ); + const aClient = new networkLayer.OutOfWorkerSocketClientPipe( + workerData.url, gArrWorkers[idxWorker] ); + gArrClients.push( aClient ); + aClient.logicalInitComplete = false; + aClient.errorLogicalInit = null; + aClient.on( "message", async function( eventData: any ): Promise { + const joMessage = eventData.message; + switch ( joMessage.method ) { + case "init": + if( !joMessage.error ) { + aClient.logicalInitComplete = true; + break; + } + aClient.errorLogicalInit = joMessage.error; + opts.details.critical( + " Loop worker thread {} reported/returned init error: {err}", + idxWorker, joMessage.error ); + break; + case "log": + log.information( "LOOP WORKER {} {}", workerData.url, joMessage.message ); + break; + case "saveTransferError": + imaTransferErrorHandling.saveTransferError( + joMessage.message.category, joMessage.message.textLog, joMessage.message.ts ); + break; + case "saveTransferSuccess": + imaTransferErrorHandling.saveTransferSuccess( joMessage.message.category ); + break; + } // switch ( joMessage.method ) + } ); + const jo: any = { + method: "init", + message: { + opts: { + imaState: { + optsLoop: getDefaultOptsLoop( idxWorker ), + verbose_: log.verboseGet(), + expose_details_: log.exposeDetailsGet(), + loopState: state.gDefaultValueForLoopState, + isPrintGathered: opts.imaState.isPrintGathered, + isPrintSecurityValues: opts.imaState.isPrintSecurityValues, + isPrintPWA: opts.imaState.isPrintPWA, + isDynamicLogInDoTransfer: opts.imaState.isDynamicLogInDoTransfer, + isDynamicLogInBlsSigner: opts.imaState.isDynamicLogInBlsSigner, + bIsNeededCommonInit: false, + bSignMessages: opts.imaState.bSignMessages, + joSChainNetworkInfo: opts.imaState.joSChainNetworkInfo, + strPathBlsGlue: opts.imaState.strPathBlsGlue, + strPathHashG1: opts.imaState.strPathHashG1, + strPathBlsVerify: opts.imaState.strPathBlsVerify, + isEnabledMultiCall: opts.imaState.isEnabledMultiCall, + bNoWaitSChainStarted: opts.imaState.bNoWaitSChainStarted, + nMaxWaitSChainAttempts: opts.imaState.nMaxWaitSChainAttempts, + nTransferBlockSizeM2S: opts.imaState.nTransferBlockSizeM2S, + nTransferBlockSizeS2M: opts.imaState.nTransferBlockSizeS2M, + nTransferBlockSizeS2S: opts.imaState.nTransferBlockSizeS2S, + nTransferStepsM2S: opts.imaState.nTransferStepsM2S, + nTransferStepsS2M: opts.imaState.nTransferStepsS2M, + nTransferStepsS2S: opts.imaState.nTransferStepsS2S, + nMaxTransactionsM2S: opts.imaState.nMaxTransactionsM2S, + nMaxTransactionsS2M: opts.imaState.nMaxTransactionsS2M, + nMaxTransactionsS2S: opts.imaState.nMaxTransactionsS2S, + + nBlockAwaitDepthM2S: opts.imaState.nBlockAwaitDepthM2S, + nBlockAwaitDepthS2M: opts.imaState.nBlockAwaitDepthS2M, + nBlockAwaitDepthS2S: opts.imaState.nBlockAwaitDepthS2S, + nBlockAgeM2S: opts.imaState.nBlockAgeM2S, + nBlockAgeS2M: opts.imaState.nBlockAgeS2M, + nBlockAgeS2S: opts.imaState.nBlockAgeS2S, + + nLoopPeriodSeconds: opts.imaState.nLoopPeriodSeconds, + nNodeNumber: opts.imaState.nNodeNumber, + nNodesCount: opts.imaState.nNodesCount, + nTimeFrameSeconds: opts.imaState.nTimeFrameSeconds, + nNextFrameGap: opts.imaState.nNextFrameGap, + + joCommunityPool: null, + joDepositBoxETH: null, + joDepositBoxERC20: null, + joDepositBoxERC721: null, + joDepositBoxERC1155: null, + joDepositBoxERC721WithMetadata: null, + joLinker: null, + isWithMetadata721: false, + + joTokenManagerETH: null, + joTokenManagerETHTarget: null, + joTokenManagerERC20: null, + joTokenManagerERC20Target: null, + joTokenManagerERC721: null, + joTokenManagerERC721Target: null, + joTokenManagerERC1155: null, + joTokenManagerERC1155Target: null, + joTokenManagerERC721WithMetadata: null, + joTokenManagerERC721WithMetadataTarget: null, + joCommunityLocker: null, + joCommunityLockerTarget: null, + joMessageProxyMainNet: null, + joMessageProxySChain: null, + joMessageProxySChainTarget: null, + joTokenManagerLinker: null, + joTokenManagerLinkerTarget: null, + joEthErc20: null, + joEthErc20Target: null, + + chainProperties: constructChainProperties( opts ), + joAbiSkaleManager: opts.imaState.joAbiSkaleManager, + bHaveSkaleManagerABI: opts.imaState.bHaveSkaleManagerABI, + strChainNameOriginChain: opts.imaState.strChainNameOriginChain, + isPWA: opts.imaState.isPWA, + nTimeoutSecondsPWA: opts.imaState.nTimeoutSecondsPWA, + strReimbursementChain: opts.imaState.strReimbursementChain, + isShowReimbursementBalance: opts.imaState.isShowReimbursementBalance, + nReimbursementRecharge: opts.imaState.nReimbursementRecharge, + nReimbursementWithdraw: opts.imaState.nReimbursementWithdraw, + nReimbursementRange: opts.imaState.nReimbursementRange, + joSChainDiscovery: { + isSilentReDiscovery: + opts.imaState.joSChainDiscovery.isSilentReDiscovery, + repeatIntervalMilliseconds: + opts.imaState.joSChainDiscovery.repeatIntervalMilliseconds, + periodicDiscoveryInterval: + opts.imaState.joSChainDiscovery.periodicDiscoveryInterval + }, + optsS2S: { // S-Chain to S-Chain transfer options + isEnabled: true, + strNetworkBrowserPath: opts.imaState.optsS2S.strNetworkBrowserPath + }, + nJsonRpcPort: opts.imaState.nJsonRpcPort, + isCrossImaBlsMode: opts.imaState.isCrossImaBlsMode + } + }, + colorization: { isEnabled: log.isEnabledColorization() } + } + }; + while( !aClient.logicalInitComplete ) { + log.information( "LOOP server is not initialized yet..." ); + await threadInfo.sleep( 1000 ); + aClient.send( jo ); + } + } + log.debug( "Loop module did created its ", + gArrWorkers.length, " worker(s) in ", threadInfo.threadDescription() ); +} + +export async function runParallelLoops( opts: TParallelLoopRunOptions ): Promise { + log.notice( "Will start parallel IMA transfer loops in {}...", threadInfo.threadDescription() ); + await ensureHaveWorkers( opts ); + log.success( "Done, did started parallel IMA transfer loops in {}, have {} worker(s) and {} " + + "clients(s).", threadInfo.threadDescription(), gArrWorkers.length, gArrClients.length ); + return true; +} + +export async function spreadArrivedStateOfPendingWorkAnalysis( joMessage: any ): Promise { + if( !( joMessage && typeof joMessage === "object" && + "method" in joMessage && joMessage.method == "skale_imaNotifyLoopWork" ) + ) + return; + const cntWorkers = gArrWorkers.length; + for( let idxWorker = 0; idxWorker < cntWorkers; ++idxWorker ) + gArrClients[idxWorker].send( joMessage ); +} + +export async function spreadUpdatedSChainNetwork( isFinal: boolean ): Promise { + const imaState: state.TIMAState = state.get(); + const joMessage: any = { + method: "spreadUpdatedSChainNetwork", + isFinal: ( !!isFinal ), + joSChainNetworkInfo: imaState.joSChainNetworkInfo + }; + const cntWorkers = gArrWorkers.length; + for( let idxWorker = 0; idxWorker < cntWorkers; ++idxWorker ) + gArrClients[idxWorker].send( joMessage ); +} diff --git a/src/loopWorker.ts b/src/loopWorker.ts new file mode 100644 index 00000000..db98ff31 --- /dev/null +++ b/src/loopWorker.ts @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file loopWorker.ts + * @copyright SKALE Labs 2019-Present + */ + +import { parentPort, workerData } from "worker_threads"; +import * as networkLayer from "./socket.js"; +import { SocketServer } from "./socketServer.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as loop from "./loop.js"; +import * as imaTx from "./imaTx.js"; +import * as imaTransferErrorHandling from "./imaTransferErrorHandling.js"; +import * as imaCLI from "./cli.js"; +import * as state from "./state.js"; +import * as pwa from "./pwa.js"; +import * as log from "./log.js"; +import * as threadInfo from "./threadInfo.js"; + +let imaState: state.TIMAState = state.get(); + +if( parentPort ) { + parentPort.on( "message", jo => { + networkLayer.inWorkerAPIs.onMessage( jo ); + } ); +} + +function doSendMessage( type: any, endpoint: any, workerUUID: any, data: any ): void { + const jo: any = networkLayer.socketReceivedDataReverseMarshall( data ); + const joSend: any = { + workerMessageType: + ( type && typeof type === "string" && type.length > 0 ) + ? type + : "inWorkerMessage", + workerEndPoint: endpoint, + workerUUID, + data: jo + }; + if( parentPort ) + parentPort.postMessage( networkLayer.socketSentDataMarshall( joSend ) ); +} + +class ObserverServer extends SocketServer { + initComplete?: boolean; + opts: loop.TParallelLoopRunOptions | null; + intervalPeriodicSchainsCaching?: number | null; + bIsPeriodicCachingStepInProgress?: boolean; + constructor ( acceptor: any ) { + super( acceptor ); + this.opts = null; + const self: any = this; + self.initComplete = false; + log.enableColorization( workerData.colorization.isEnabled ); + self.intervalPeriodicSchainsCaching = null; + self.bIsPeriodicCachingStepInProgress = false; + self.mapApiHandlers.init = + function( joMessage: any, joAnswer: any, eventData: any, socket: any ): any { + joAnswer.message = { + method: joMessage.method.toString(), + error: null + }; + if( self.initComplete ) + return joAnswer; + self.log = function(): void { + const args = Array.prototype.slice.call( arguments ); + const jo: any = { + method: "log", + error: null, + message: args.join( " " ) + }; + const isFlush = true; + socket.send( jo, isFlush ); + }; + self.initLogMethods(); + self.opts = JSON.parse( JSON.stringify( joMessage.message.opts ) ); + self.opts.details = { + write: self.log, + fatal: self.fatal, + critical: self.critical, + error: self.error, + warning: self.warning, + attention: self.attention, + information: self.information, + info: self.info, + notice: self.notice, + note: self.note, + debug: self.debug, + trace: self.trace, + success: self.success + }; + log.enableColorization( joMessage.message.colorization.isEnabled ); + log.verboseSet( self.opts.imaState.verbose_ ); + log.exposeDetailsSet( self.opts.imaState.expose_details_ ); + imaTransferErrorHandling.saveTransferEvents.on( "error", + function( eventData: any ): void { + const jo: any = { + method: "saveTransferError", + message: eventData.detail + }; + const isFlush = true; + socket.send( jo, isFlush ); + } ); + imaTransferErrorHandling.saveTransferEvents.on( "success", + function( eventData: any ): void { + const jo: any = { + method: "saveTransferSuccess", + message: eventData.detail + }; + const isFlush = true; + socket.send( jo, isFlush ); + } ); + self.opts.imaState.chainProperties.mn.joAccount.address = + function(): string { return owaspUtils.fnAddressImpl_( this ); }; + self.opts.imaState.chainProperties.sc.joAccount.address = + function(): string { return owaspUtils.fnAddressImpl_( this ); }; + if( self.opts.imaState.chainProperties.mn.strURL && + typeof self.opts.imaState.chainProperties.mn.strURL === "string" && + self.opts.imaState.chainProperties.mn.strURL.length > 0 + ) { + const u = self.opts.imaState.chainProperties.mn.strURL; + self.opts.imaState.chainProperties.mn.ethersProvider = + owaspUtils.getEthersProviderFromURL( u ); + } else { + self.warning( + "WARNING: No Main-net URL specified in command line arguments(needed for " + + "particular operations only) in {}", threadInfo.threadDescription() ); + } + + if( self.opts.imaState.chainProperties.sc.strURL && + typeof self.opts.imaState.chainProperties.sc.strURL === "string" && + self.opts.imaState.chainProperties.sc.strURL.length > 0 + ) { + const u = self.opts.imaState.chainProperties.sc.strURL; + self.opts.imaState.chainProperties.sc.ethersProvider = + owaspUtils.getEthersProviderFromURL( u ); + } else { + self.warning( + "WARNING: No Main-net URL specified in command line arguments(needed for " + + "particular operations only) in {}", threadInfo.threadDescription() ); + } + + self.opts.imaState.optsLoop.joRuntimeOpts.isInsideWorker = true; + imaState = self.opts.imaState; + imaState.chainProperties.mn.ethersProvider = null; + imaState.chainProperties.sc.ethersProvider = null; + imaState.chainProperties.tc.ethersProvider = null; + imaState.chainProperties.mn.transactionCustomizer = + imaTx.getTransactionCustomizerForMainNet(); + imaState.chainProperties.sc.transactionCustomizer = + imaTx.getTransactionCustomizerForSChain(); + imaState.chainProperties.tc.transactionCustomizer = + imaTx.getTransactionCustomizerForSChainTarget(); + state.set( imaState ); + imaCLI.initContracts(); + self.initComplete = true; + self.information( "IMA loop worker ", workerData.url, + " will do the following work:\n Oracle operations.....", + log.yn( self.opts.imaState.optsLoop.enableStepOracle ), "\n", + " M2S", log.fmtDebug( " transfers........." ), + log.yn( self.opts.imaState.optsLoop.enableStepM2S ), "\n" + + " S2M", log.fmtDebug( " transfers........." ), + log.yn( self.opts.imaState.optsLoop.enableStepS2M ), "\n", + " S2S", log.fmtDebug( " transfers........." ), + log.yn( self.opts.imaState.optsLoop.enableStepS2S ) ); + /* await */ + loop.runTransferLoop( self.opts.imaState.optsLoop ) + .then( function(): void {} ).catch( function(): void {} ); + self.information( "Full init compete for in-worker IMA loop {} in {}", + workerData.url, threadInfo.threadDescription() ); + return joAnswer; + }; + self.mapApiHandlers.spreadUpdatedSChainNetwork = + function( joMessage: any, joAnswer: any, eventData: any, socket: any ): void { + self.initLogMethods(); + self.debug( + "New own S-Chains network information is arrived to {} loop worker " + + "in {}: {}, this own S-Chain update is {}", workerData.url, + threadInfo.threadDescription(), joMessage.joSChainNetworkInfo, + log.posNeg( joMessage.isFinal, "final", "partial" ) ); + imaState.joSChainNetworkInfo = joMessage.joSChainNetworkInfo; + }; + self.mapApiHandlers.skale_imaNotifyLoopWork = + function( joMessage: any, joAnswer: any, eventData: any, socket: any ): void { + self.initLogMethods(); + pwa.handleLoopStateArrived( // NOTICE: no await here, executed async + imaState, + owaspUtils.toInteger( joMessage.params.nNodeNumber ), + joMessage.params.strLoopWorkType, + joMessage.params.nIndexS2S, + ( !!( joMessage.params.isStart ) ), + owaspUtils.toInteger( joMessage.params.ts ), + joMessage.params.signature + ).then( function(): void {} ).catch( function(): void {} ); + }; + console.log( + `Initialized in-worker IMA loop ${workerData.url} server ` + + `in ${threadInfo.threadDescription()}` ); + } + + dispose(): void { + const self: any = this; + self.isDisposing = true; + if( self.intervalPeriodicSchainsCaching ) { + clearInterval( self.intervalPeriodicSchainsCaching ); + self.intervalPeriodicSchainsCaching = null; + } + super.dispose(); + } + + initLogMethods(): void { + const self: any = this; + if( "fatal" in self && self.fatal && typeof self.fatal === "function" ) + return; + self.fatal = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "fatal" ) ) { + self.log( log.getLogLinePrefixFatal() + + log.fmtFatal( ...args ) ); + } + }; + self.critical = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "critical" ) ) { + self.log( log.getLogLinePrefixCritical() + + log.fmtCritical( ...args ) ); + } + }; + self.error = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "error" ) ) { + self.log( log.getLogLinePrefixError() + + log.fmtError( ...args ) ); + } + }; + self.warning = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "warning" ) ) { + self.log( log.getLogLinePrefixWarning() + + log.fmtWarning( ...args ) ); + } + }; + self.attention = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "attention" ) ) { + self.log( log.getLogLinePrefixAttention() + + log.fmtAttention( ...args ) ); + } + }; + self.information = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "information" ) ) { + self.log( log.getLogLinePrefixInformation() + + log.fmtInformation( ...args ) ); + } + }; + self.info = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "information" ) ) { + self.log( log.getLogLinePrefixInformation() + + log.fmtInformation( ...args ) ); + } + }; + self.notice = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "notice" ) ) { + self.log( log.getLogLinePrefixNotice() + + log.fmtNotice( ...args ) ); + } + }; + self.note = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "notice" ) ) { + self.log( log.getLogLinePrefixNote() + + log.fmtNote( ...args ) ); + } + }; + self.debug = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "debug" ) ) { + self.log( log.getLogLinePrefixDebug() + + log.fmtDebug( ...args ) ); + } + }; + self.trace = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "trace" ) ) { + self.log( log.getLogLinePrefixTrace() + + log.fmtTrace( ...args ) ); + } + }; + self.success = function( ...args: any[] ) { + if( log.verboseGet() >= log.verboseName2Number( "information" ) ) { + self.log( log.getLogLinePrefixSuccess() + + log.fmtSuccess( ...args ) ); + } + }; + } +}; + +const acceptor = new networkLayer.InWorkerSocketServerAcceptor( workerData.url, doSendMessage ); +const server = new ObserverServer( acceptor ); +server.on( "dispose", function(): void { + const self: any = server; + self.debug( "Disposed in-worker in {} IMA loop {}", + threadInfo.threadDescription(), workerData.url ); +} ); diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 00000000..ddbe6af8 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file main.ts + * @copyright SKALE Labs 2019-Present + */ + +import express from "express"; +import bodyParser from "body-parser"; +import * as ws from "ws"; +import * as owaspUtils from "./owaspUtils.js"; +import * as log from "./log.js"; +import * as imaCLI from "./cli.js"; +import * as loop from "./loop.js"; +import * as imaHelperAPIs from "./imaHelperAPIs.js"; +import * as imaTransferErrorHandling from "./imaTransferErrorHandling.js"; +import * as imaBLS from "./bls.js"; +import * as pwa from "./pwa.js"; +import * as clpTools from "./clpTools.js"; +import * as discoveryTools from "./discoveryTools.js"; +import * as skaleObserver from "./observer.js"; + +import * as state from "./state.js"; + +// allow self-signed wss and https +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +process.on( "unhandledRejection", function( reason: any, p: any ): void { + log.fatal( + "CRITICAL ERROR: unhandled rejection with reason {} and promise {}", + reason, p ); +} ).on( "uncaughtException", function( err: any ): void { + log.fatal( + "CRITICAL ERROR: uncaught exception: {err}, stack is:\n{stack}", + err, err ); + process.exit( 1 ); +} ); + +function parseCommandLine(): void { + const imaState: state.TIMAState = state.get(); + log.autoEnableColorizationFromCommandLineArgs(); + const strPrintedArguments = process.argv.join( " " ); + imaCLI.parse( { + register: clpTools.commandLineTaskRegister, + register1: clpTools.commandLineTaskRegister1, + "check-registration": clpTools.commandLineTaskCheckRegistration, + "check-registration1": clpTools.commandLineTaskCheckRegistration1, + "mint-erc20": clpTools.commandLineTaskMintErc20, + "mint-erc721": clpTools.commandLineTaskMintErc721, + "mint-erc1155": clpTools.commandLineTaskMintErc1155, + "burn-erc20": clpTools.commandLineTaskBurnErc20, + "burn-erc721": clpTools.commandLineTaskBurnErc721, + "burn-erc1155": clpTools.commandLineTaskBurnErc1155, + "show-balance": clpTools.commandLineTaskShowBalance, + "m2s-payment": clpTools.commandLineTaskPaymentM2S, + "s2m-payment": clpTools.commandLineTaskPaymentS2M, + "s2s-payment": clpTools.commandLineTaskPaymentS2S, + "s2m-receive": clpTools.commandLineTaskReceiveS2M, + "s2m-view": clpTools.commandLineTaskViewS2M, + "m2s-transfer": clpTools.commandLineTaskTransferM2S, + "s2m-transfer": clpTools.commandLineTaskTransferS2M, + "s2s-transfer": clpTools.commandLineTaskTransferS2S, + transfer: clpTools.commandLineTaskTransfer, + loop: clpTools.commandLineTaskLoop, + "simple-loop": clpTools.commandLineTaskLoopSimple, + "browse-s-chain": clpTools.commandLineTaskBrowseSChain + } ); + let haveReimbursementCommands = false; + if( imaState.isShowReimbursementBalance ) { + haveReimbursementCommands = true; + log.trace( "Will require reimbursement chain name to show reimbursement balance" ); + clpTools.commandLineTaskReimbursementShowBalance(); + } + if( imaState.isReimbursementEstimate ) { + haveReimbursementCommands = true; + log.trace( "Will require reimbursement chain name to do reimbursement estimation" ); + clpTools.commandLineTaskReimbursementEstimateAmount(); + } + if( imaState.nReimbursementRecharge ) { + haveReimbursementCommands = true; + log.trace( "Will require reimbursement chain name to do reimbursement recharge" ); + clpTools.commandLineTaskReimbursementRecharge(); + } + if( imaState.nReimbursementWithdraw ) { + haveReimbursementCommands = true; + log.trace( "Will require reimbursement chain name to do reimbursement withdraw" ); + clpTools.commandLineTaskReimbursementWithdraw(); + } + if( haveReimbursementCommands ) { + if( imaState.strReimbursementChain == "" ) { + log.fatal( "Runtime init error: missing value for reimbursement-chain parameter, " + + "must be non-empty chain name" ); + process.exit( 163 ); + } + } + if( imaState.nReimbursementRange >= 0 ) + clpTools.commandLineTaskReimbursementSetRange(); + if( imaState.nAutoExitAfterSeconds > 0 ) { + log.warning( "Automatic exit after {} second(s) is requested.", + imaState.nAutoExitAfterSeconds ); + const iv = owaspUtils.setInterval2( function(): void { + log.warning( "Performing automatic exit after {} second(s)...", + imaState.nAutoExitAfterSeconds ); + owaspUtils.clearInterval2( iv ); + process.exit( 0 ); + }, imaState.nAutoExitAfterSeconds * 1000 ); + } else + log.warning( "Automatic exit was not requested, skipping it." ); + if( imaState.strLogFilePath.length > 0 ) { + log.information( "Will print message to file {}", imaState.strLogFilePath ); + log.add( imaState.strLogFilePath, imaState.nLogMaxSizeBeforeRotation, + imaState.nLogMaxFilesCount ); + } + log.information( "Agent was started with {} command line argument(s) as: {}", + process.argv.length, strPrintedArguments ); + if( imaState.bIsNeededCommonInit ) { + imaCLI.commonInit(); + imaCLI.initContracts(); + } + if( imaState.bShowConfigMode ) { + // just show configuration values and exit + process.exit( 0 ); + } +} + +let gServerMonitoringWS: any = null; + +function initMonitoringServer(): void { + const imaState: state.TIMAState = state.get(); + if( imaState.nMonitoringPort <= 0 ) + return; + const strLogPrefix = "Monitoring: "; + if( imaState.bLogMonitoringServer ) { + log.trace( "{p}Will start monitoring WS server on port {}", + strLogPrefix, imaState.nMonitoringPort ); + } + try { + gServerMonitoringWS = new ws.WebSocketServer( { port: imaState.nMonitoringPort } ); + } catch ( err ) { + log.error( "Failed start monitoring WS server on port {}, error is: {err}", + imaState.nMonitoringPort, err ); + } + gServerMonitoringWS.on( "connection", function( wsPeer: any, req: any ): void { + let ip = req.socket.remoteAddress; + if( "headers" in req && req.headers && typeof req.headers === "object" && + "x-forwarded-for" in req.headers && req.headers["x-forwarded-for"] ) + ip = req.headers["x-forwarded-for"]; // better under NGINX + if( ( !ip ) && "_socket" in req && req._socket && "remoteAddress" in req._socket ) + ip = req._socket.remoteAddress; + if( !ip ) + ip = "N/A"; + if( imaState.bLogMonitoringServer ) + log.debug( "{p}New connection from {}", strLogPrefix, ip ); + wsPeer.on( "message", function( message: any ): void { + const joAnswer: any = { + method: null, + id: null, + error: null + }; + try { + const joMessage: any = JSON.parse( message ); + if( imaState.bLogMonitoringServer ) + log.trace( "{p}<<< message from {}: {}", strLogPrefix, ip, joMessage ); + + if( !( "method" in joMessage ) ) + throw new Error( "\"method\" field was not specified" ); + joAnswer.method = joMessage.method; + if( !( "id" in joMessage ) ) + throw new Error( "\"id\" field was not specified" ); + joAnswer.id = joMessage.id; + switch ( joMessage.method ) { + case "echo": + case "ping": + break; + case "get_schain_network_info": + joAnswer.schain_network_info = imaState.joSChainNetworkInfo; + break; + case "get_runtime_params": + { + joAnswer.runtime_params = {}; + const arrRuntimeParamNames = [ + "bNoWaitSChainStarted", + "nMaxWaitSChainAttempts", + + "nTransferBlockSizeM2S", + "nTransferBlockSizeS2M", + "nTransferBlockSizeS2S", + "nTransferStepsM2S", + "nTransferStepsS2M", + "nTransferStepsS2S", + "nMaxTransactionsM2S", + "nMaxTransactionsS2M", + "nMaxTransactionsS2S", + + "nBlockAwaitDepthM2S", + "nBlockAwaitDepthS2M", + "nBlockAwaitDepthS2S", + "nBlockAgeM2S", + "nBlockAgeS2M", + "nBlockAgeS2S", + + "nLoopPeriodSeconds", + + "nNodeNumber", + "nNodesCount", + "nTimeFrameSeconds", + "nNextFrameGap", + + "isPWA", + + "nMonitoringPort" + ]; + for( const paramName of arrRuntimeParamNames ) { + if( paramName in imaState ) + joAnswer.runtime_params[paramName] = ( imaState as any )[paramName]; + } + } break; + case "get_last_transfer_errors": + joAnswer.last_transfer_errors = imaTransferErrorHandling.getLastTransferErrors( + !!( ( ( "isIncludeTextLog" in joMessage ) && + joMessage.isIncludeTextLog ) ) ); + joAnswer.last_error_categories = + imaTransferErrorHandling.getLastErrorCategories(); + break; + default: + throw new Error( `Unknown method name ${joMessage.method} was specified` ); + } // switch( joMessage.method ) + } catch ( err ) { + log.error( "{p}Bad message from {}: {}, error is: {err}, stack is:\n{stack}", + strLogPrefix, ip, message, err, err ); + } + try { + if( imaState.bLogMonitoringServer ) + log.trace( "{p}>>> answer to {}: {}", strLogPrefix, ip, joAnswer ); + wsPeer.send( JSON.stringify( joAnswer ) ); + } catch ( err ) { + log.error( "{p}Failed to sent answer to {}, error is: {err}, stack is:\n{stack}", + strLogPrefix, ip, err, err ); + } + } ); + } ); +} + +let gExpressJsonRpcAppIMA: any = null; + +function initJsonRpcServer(): void { + const imaState: state.TIMAState = state.get(); + if( imaState.nJsonRpcPort <= 0 ) + return; + const strLogPrefix = "JSON RPC: "; + gExpressJsonRpcAppIMA = express(); + gExpressJsonRpcAppIMA.use( bodyParser.urlencoded( { extended: true } ) ); + gExpressJsonRpcAppIMA.use( bodyParser.json() ); + gExpressJsonRpcAppIMA.post( "/", async function( req: any, res: any ): Promise { + const isSkipMode = false; + const message = JSON.stringify( req.body ); + const ip = req.connection.remoteAddress.split( ":" ).pop(); + const fnSendAnswer: any = function( joAnswer: any ): void { + try { + res.header( "Content-Type", "application/json" ); + res.status( 200 ).send( JSON.stringify( joAnswer ) ); + log.trace( "{p}>>> did sent answer to {}: ", strLogPrefix, ip, joAnswer ); + } catch ( err ) { + log.error( "{p}Failed to sent answer {} to {}, error is: {err}, stack is:\n{stack}", + strLogPrefix, joAnswer, ip, err, err ); + } + }; + let joAnswer: any = { + method: null, + id: null, + error: null + }; + try { + const joMessage: any = JSON.parse( message ); + log.trace( "{p}<<< Peer message from {}: ", strLogPrefix, ip, joMessage ); + if( !( "method" in joMessage ) ) + throw new Error( "\"method\" field was not specified" ); + joAnswer.method = joMessage.method; + if( !( "id" in joMessage ) ) + throw new Error( "\"id\" field was not specified" ); + if( "id" in joMessage ) + joAnswer.id = joMessage.id; + if( "method" in joMessage ) + joAnswer.method = joMessage.method.toString(); + switch ( joMessage.method ) { + case "echo": + joAnswer.result = "echo"; + fnSendAnswer( joAnswer ); + break; + case "ping": + joAnswer.result = "pong"; + fnSendAnswer( joAnswer ); + break; + case "skale_imaVerifyAndSign": + joAnswer = await imaBLS.handleSkaleImaVerifyAndSign( joMessage ); + break; + case "skale_imaBSU256": + joAnswer = await imaBLS.handleSkaleImaBSU256( joMessage ); + break; + case "skale_imaNotifyLoopWork": + if( await pwa.handleLoopStateArrived( + imaState, + owaspUtils.toInteger( joMessage.params.nNodeNumber ), + joMessage.params.strLoopWorkType, + joMessage.params.nIndexS2S, + ( !!( joMessage.params.isStart ) ), + owaspUtils.toInteger( joMessage.params.ts ), + joMessage.params.signature + ) ) + await loop.spreadArrivedStateOfPendingWorkAnalysis( joMessage ); + + break; + case "skale_getCachedSNB": + joAnswer.arrSChainsCached = skaleObserver.getLastCachedSChains(); + break; + default: + joAnswer.error = `Unknown method name ${joMessage.method} was specified`; + break; + } // switch( joMessage.method ) + if( ( !joAnswer ) || typeof joAnswer !== "object" ) { + joAnswer = {}; + joAnswer.error = "internal error, null data returned"; + } + } catch ( err ) { + log.error( "{p}Bad message from {}: {}, error is: {err}, stack is:\n{stack}", + strLogPrefix, ip, message, err, err ); + } + if( !isSkipMode ) + fnSendAnswer( joAnswer ); + } ); + gExpressJsonRpcAppIMA.listen( imaState.nJsonRpcPort ); +} + +async function doTheJob(): Promise { + const imaState: state.TIMAState = state.get(); + const strLogPrefix = "Job 1: "; + let idxAction = 0; + const cntActions = imaState.arrActions.length; + let cntFalse = 0; + let cntTrue = 0; + for( idxAction = 0; idxAction < cntActions; ++idxAction ) { + log.information( "{p}{p}", strLogPrefix, imaHelperAPIs.longSeparator ); + const joAction = imaState.arrActions[idxAction]; + log.debug( "{p}Will execute action: {bright} ({} of {})", + strLogPrefix, joAction.name, idxAction + 1, cntActions ); + try { + if( await joAction.fn() ) { + ++cntTrue; + log.success( "{p}Succeeded action: {bright}", strLogPrefix, joAction.name ); + } else { + ++cntFalse; + log.error( "{p}Failed action: {bright}", strLogPrefix, joAction.name ); + } + } catch ( err ) { + ++cntFalse; + log.critical( "{p}Exception occurred while executing action: {err}, stack is:\n{stack}", + strLogPrefix, err, err ); + } + } + log.information( "{p}{p}", strLogPrefix, imaHelperAPIs.longSeparator ); + log.information( "{p}{}", strLogPrefix, "FINISH:" ); + log.information( "{p}task(s) executed {}", strLogPrefix, cntActions ); + log.information( "{p}{}{}", strLogPrefix, cntTrue, log.fmtSuccess( " task(s) succeeded" ) ); + log.information( "{p}{}{}", strLogPrefix, cntFalse, log.fmtError( " task(s) failed" ) ); + log.information( "{p}{p}", strLogPrefix, imaHelperAPIs.longSeparator ); + process.exitCode = ( cntFalse > 0 ) ? cntFalse : 0; + if( !state.isPreventExitAfterLastAction() ) + process.exit( process.exitCode ); +} + +function handleFirstSChainDiscoveryAttemptDone( + err: any, joSChainNetworkInfo: discoveryTools.TSChainNetworkInfo, + isSilentReDiscovery: boolean, fnOnPeriodicDiscoveryResultAvailable: any ): void { + if( err ) { + // error information is printed by discoveryTools.discoverSChainNetwork() + process.exit( 166 ); + } + log.success( "S-Chain network was discovered: {}", joSChainNetworkInfo ); + const imaState: state.TIMAState = state.get(); + imaState.joSChainNetworkInfo = joSChainNetworkInfo; + discoveryTools.continueSChainDiscoveryInBackgroundIfNeeded( + isSilentReDiscovery, function(): void { + discoveryTools.doPeriodicSChainNetworkDiscoveryIfNeeded( + isSilentReDiscovery, fnOnPeriodicDiscoveryResultAvailable ) + .then( function(): void {} ).catch( function(): void {} ); + } ).then( function(): void {} ).catch( function(): void {} ); + imaState.joSChainNetworkInfo = joSChainNetworkInfo; +} + +async function main(): Promise { + log.autoEnableColorizationFromCommandLineArgs(); + const imaState: state.TIMAState = state.get(); + const strTmpAddressFromEnvMainNet = + owaspUtils.toEthPrivateKey( process.env.ACCOUNT_FOR_ETHEREUM ); + const strTmpAddressFromEnvSChain = + owaspUtils.toEthPrivateKey( process.env.ACCOUNT_FOR_SCHAIN ); + const strTmpAddressFromEnvSChainTarget = + owaspUtils.toEthPrivateKey( process.env.ACCOUNT_FOR_SCHAIN_TARGET ); + if( strTmpAddressFromEnvMainNet && + typeof strTmpAddressFromEnvMainNet === "string" && + strTmpAddressFromEnvMainNet.length > 0 ) + imaState.chainProperties.mn.joAccount.address_ = strTmpAddressFromEnvMainNet.toString(); + if( strTmpAddressFromEnvSChain && + typeof strTmpAddressFromEnvSChain === "string" && + strTmpAddressFromEnvSChain.length > 0 ) + imaState.chainProperties.sc.joAccount.address_ = strTmpAddressFromEnvSChain.toString(); + if( strTmpAddressFromEnvSChainTarget && + typeof strTmpAddressFromEnvSChainTarget === "string" && + strTmpAddressFromEnvSChainTarget.length > 0 ) { + imaState.chainProperties.tc.joAccount.address_ = + strTmpAddressFromEnvSChainTarget.toString(); + } + parseCommandLine(); + initMonitoringServer(); + initJsonRpcServer(); + const isSilentReDiscovery = imaState.isPrintSecurityValues + ? false + : imaState.joSChainDiscovery.isSilentReDiscovery; + const fnOnPeriodicDiscoveryResultAvailable = function( isFinal: boolean ): void { + loop.spreadUpdatedSChainNetwork( isFinal ) + .then( function(): void {} ).catch( function(): void {} ); + }; + if( imaState.bSignMessages ) { + if( imaState.strPathBlsGlue.length == 0 ) { + log.fatal( "Please specify {} command line parameter.", "--bls-glue" ); + process.exit( 164 ); + } + if( imaState.strPathHashG1.length == 0 ) { + log.fatal( "Please specify {} command line parameter.", "--hash-g1" ); + process.exit( 165 ); + } + log.information( "S-Chain network was discovery uses {} mode", + ( isSilentReDiscovery + ? log.fmtWarning( "silent" ) + : log.fmtSuccess( "exposed details" ) ) ); + if( !imaState.bNoWaitSChainStarted ) { + await discoveryTools.waitUntilSChainStarted(); + if( !isSilentReDiscovery ) { + log.information( + "This S-Chain discovery will be done for command line task handler" ); + } + const nCountToWait = -1; + discoveryTools.discoverSChainNetwork( + function( err?: Error | string | null, + joSChainNetworkInfo?: discoveryTools.TSChainNetworkInfo | null ): void { + if( joSChainNetworkInfo ) { + handleFirstSChainDiscoveryAttemptDone( + err, joSChainNetworkInfo, isSilentReDiscovery, + fnOnPeriodicDiscoveryResultAvailable ); + } + doTheJob().then( function(): void {} ).catch( function(): void {} ); + // Finish of IMA Agent startup, + // everything else is in async calls executed later + }, isSilentReDiscovery, imaState.joSChainNetworkInfo, nCountToWait + ).catch( function( err: Error | string ): void { + const strError = owaspUtils.extractErrorMessage( err ); + log.critical( "S-Chain network discovery failed: {err}", strError ); + doTheJob().then( function(): void {} ).catch( function(): void {} ); + } ); + } + } else { + discoveryTools.doPeriodicSChainNetworkDiscoveryIfNeeded( + isSilentReDiscovery, fnOnPeriodicDiscoveryResultAvailable ) + .then( function(): void {} ).catch( function(): void {} ); + doTheJob().then( function(): void {} ).catch( function(): void {} ); + // Finish of IMA Agent startup, + // everything else is in async calls executed later, + // skip exit here to avoid early termination while tasks ase still running + } +} + +main().then( function(): void {} ).catch( function(): void {} ); diff --git a/src/observer.ts b/src/observer.ts new file mode 100644 index 00000000..ffdd4051 --- /dev/null +++ b/src/observer.ts @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file observer.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as state from "./state.js"; +import * as imaUtils from "./utils.js"; +import * as log from "./log.js"; +import * as threadInfo from "./threadInfo.js"; + +export interface TSChainNode { + name: string + ip: string + publicIP: string + port: string + startBlock: string + lastRewardDate: string + finishTime: string + status: string + validatorId: string + domainName: string + schainHashes: string[] + endpoints: { + ports: { + http: string + https: string + ws: string + wss: string + infoHttp: string + } + domain: { + http: string + https: string + ws: string + wss: string + infoHttp: string + } + ip: { + http: string + https: string + ws: string + wss: string + infoHttp: string + } + } +} + +export interface TSChainInformation { + name: string + mainnetOwner: string + indexInOwnerList: string + partOfNode: string + lifetime: string + startDate: string + startBlock: string + deposit: string + index: string + generation: string + originator: string + chainId: number + nodes: TSChainNode[] +} + +export interface TSChainsInformation { + updatedAt: number + schains: TSChainInformation[] +} + +export function findSChainIndexInArrayByName( + arrSChains: TSChainInformation[], strSChainName: string ): number { + for( let idxSChain = 0; idxSChain < arrSChains.length; ++idxSChain ) { + const joSChain = arrSChains[idxSChain]; + if( joSChain.name.toString() == strSChainName.toString() ) + return idxSChain; + } + return -1; +} + +let gConnectedChainsData: TSChainsInformation | null = null; + +export function autoUpdateLastCachedSChains(): boolean { + const imaState: state.TIMAState = state.get(); + if( !imaState.optsS2S.strNetworkBrowserPath ) + return false; + const jo: TSChainsInformation = + imaUtils.jsonFileLoad( imaState.optsS2S.strNetworkBrowserPath, null ); + if( !( jo && "schains" in jo && "updatedAt" in jo ) ) { + log.error( + "Connected S-chains cache in thread {} was not updated from file {}, bad data format", + threadInfo.threadDescription(), imaState.optsS2S.strNetworkBrowserPath ); + return false; + } + if( gConnectedChainsData && gConnectedChainsData.updatedAt == jo.updatedAt ) + return false; // assume data is same + gConnectedChainsData = jo; + log.debug( + "Connected S-chains cache in thread {} was updated from file {} with data: {}", + threadInfo.threadDescription(), imaState.optsS2S.strNetworkBrowserPath, + gConnectedChainsData ); + return true; +} + +export function getLastCachedSChains(): TSChainInformation[] { + autoUpdateLastCachedSChains(); + if( !gConnectedChainsData ) + return []; + return JSON.parse( JSON.stringify( gConnectedChainsData.schains ) ); +} + +export function pickRandomSChainNodeIndex( joSChain: TSChainInformation ): number { + const min = 0; const max = joSChain.nodes.length - 1; + const idxNode = Math.floor( Math.random() * ( max - min + 1 ) ) + min; + return idxNode; +} +export function pickRandomSChainNode( joSChain: TSChainInformation ): TSChainNode { + const idxNode = pickRandomSChainNodeIndex( joSChain ); + return joSChain.nodes[idxNode]; +} + +export function pickRandomSChainUrl( joSChain: TSChainInformation ): string { + const joNode: TSChainNode = pickRandomSChainNode( joSChain ); + return joNode.endpoints.ip.http.toString(); +} diff --git a/src/oracle.ts b/src/oracle.ts new file mode 100644 index 00000000..bee4ec22 --- /dev/null +++ b/src/oracle.ts @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file oracle.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as rpcCall from "./rpcCall.js"; +import * as threadInfo from "./threadInfo.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as sha3Module from "sha3"; +const Keccak: any = sha3Module.Keccak; +export const gConstMinPowResultLimit: number = 10000; +export const gConstMaxPowResultLimit: any = 100000; + +const gBigNumMinPowResult: any = owaspUtils.toBN( gConstMinPowResultLimit ); +const gBigNum1: any = owaspUtils.toBN( 1 ); +const gBigNum2: any = owaspUtils.toBN( 2 ); +const gBigNum256: any = owaspUtils.toBN( 256 ); +const gBigNumUpperPart: any = gBigNum2.pow( gBigNum256 ).sub( gBigNum1 ); + +function getUtcTimestampString( aDate?: Date ): string { + aDate = aDate ?? new Date(); // use now time if aDate is not specified + const nUtcUnixTimeStampWithMilliseconds = aDate.getTime(); + const t = nUtcUnixTimeStampWithMilliseconds.toString(); + return t; +} + +export function findPowNumber( + strRequestPart: string, details: log.TLogger, isVerbose?: boolean ): any { + details = details || log; + if( isVerbose ) + details.debug( "source part of request to find PoW number is {}", strRequestPart ); + + const t = getUtcTimestampString(); + let i = 0; let n = 0; let s = ""; + if( isVerbose ) + details.debug( "source t={}, this is UTC timestamp", t ); + for( ; i < gConstMaxPowResultLimit; ++i ) { + n = owaspUtils.toInteger( i ); + s = "{" + strRequestPart + ",\"time\":" + t + ",\"pow\":" + n + "}"; + + const hash = new Keccak( 256 ); + hash.update( s ); + let strHash = hash.digest( "hex" ); + strHash = owaspUtils.ensureStartsWith0x( strHash ); + + const f = owaspUtils.toBN( strHash ); + const r = gBigNumUpperPart.div( f ); + if( r.gt( gBigNumMinPowResult ) ) { + if( isVerbose ) { + details.debug( "computed n={}, this is resulting PoW number", i ); + details.debug( "computed f={}={}", f.toString(), + owaspUtils.ensureStartsWith0x( f.toString( 16 ) ) ); + details.debug( "computed r={}={}={}", "(2**256-1)/f", r.toString(), + owaspUtils.ensureStartsWith0x( r.toString( 16 ) ) ); + details.debug( "computed s={}", s ); + } + break; + } + } + return s; +} + +async function handleOracleCheckResultResult( + oracleOpts: any, details: log.TLogger, isVerboseTraceDetails: boolean, + joCall: rpcCall.TRPCCall, joIn: any, joOut: any +): Promise { + if( isVerboseTraceDetails ) + details.debug( "RPC call(oracle_checkResult) result is: {}", joOut ); + if( !( "result" in joOut && typeof joOut.result === "string" && + joOut.result.length > 0 ) ) { + if( isVerboseTraceDetails ) + details.error( "Bad unexpected result in oracle_checkResult" ); + await joCall.disconnect(); + return; + } + const joResult: any = JSON.parse( joOut.result ); + if( isVerboseTraceDetails ) + details.debug( "RPC call(oracle_checkResult) parsed result field is: {}", joResult ); + const gp = owaspUtils.toBN( joResult.rslts[0] ); + if( isVerboseTraceDetails ) { + details.success( "success, computed Gas Price={}={}", + gp.toString(), owaspUtils.ensureStartsWith0x( gp.toString( 16 ) ) ); + } + await joCall.disconnect(); + return gp; +} + +async function handleOracleSubmitRequestResult( + oracleOpts: any, details: log.TLogger, isVerboseTraceDetails: boolean, + joCall: rpcCall.TRPCCall, joIn: any, joOut: any +): Promise { + const nMillisecondsSleepBefore = "nMillisecondsSleepBefore" in oracleOpts + ? oracleOpts.nMillisecondsSleepBefore + : 1000; + const nMillisecondsSleepPeriod = "nMillisecondsSleepPeriod" in oracleOpts + ? oracleOpts.nMillisecondsSleepPeriod + : 3000; + let cntAttempts = "cntAttempts" in oracleOpts ? oracleOpts.cntAttempts : 40; + if( cntAttempts < 1 ) + cntAttempts = 1; + if( isVerboseTraceDetails ) + details.debug( "RPC call(oracle_submitRequest) result is: {}", joOut ); + if( !( "result" in joOut && typeof joOut.result === "string" && + joOut.result.length > 0 ) ) { + await joCall.disconnect(); + details.error( "Bad unexpected result(oracle_submitRequest), malformed " + + "non-successful result is {}", joOut ); + throw new Error( "ORACLE ERROR: Bad unexpected result(oracle_submitRequest)" ); + } + let gp = null; + for( let idxAttempt = 0; idxAttempt < cntAttempts; ++idxAttempt ) { + const nMillisecondsToSleep = ( !idxAttempt ) + ? nMillisecondsSleepBefore + : nMillisecondsSleepPeriod; + if( nMillisecondsToSleep > 0 ) + await threadInfo.sleep( nMillisecondsToSleep ); + try { + const joIn: any = { method: "oracle_checkResult", params: [ joOut.result ] }; + if( isVerboseTraceDetails ) { + details.debug( "RPC call oracle_checkResult attempt {} " + + "of {}...", idxAttempt, cntAttempts ); + details.debug( "RPC call(oracle_checkResult) is {}", joIn ); + } + gp = null; + joOut = await joCall.call( joIn ); + gp = await handleOracleCheckResultResult( + oracleOpts, details, isVerboseTraceDetails, joCall, joIn, joOut ); + if( gp ) + return gp; + } catch ( err ) { + details.critical( + "RPC call {} exception is: {err},stack is:\n{stack}", + "oracle_checkResult", err, err ); + await joCall.disconnect(); + throw err; + } + } + await joCall.disconnect(); + details.error( "RPC call(oracle_checkResult) all attempts timed out" ); + throw new Error( "RPC call(oracle_checkResult) all attempts timed out" ); +} + +export async function oracleGetGasPrice( + oracleOpts: any, details: log.TLogger ): Promise { + details = details || log; + const url: string = oracleOpts.url; + let gp: any = null; + let joCall: rpcCall.TRPCCall | null = null; + try { + const isVerbose = "isVerbose" in oracleOpts ? oracleOpts.isVerbose : false; + let isVerboseTraceDetails = "isVerboseTraceDetails" in oracleOpts + ? oracleOpts.isVerboseTraceDetails + : false; + if( !( log.verboseGet() >= log.verboseName2Number( "trace" ) ) ) + isVerboseTraceDetails = false; + const callOpts = "callOpts" in oracleOpts ? oracleOpts.callOpts : { }; + joCall = await rpcCall.create( url, callOpts || { } ); + if( !joCall ) + throw new Error( `Failed to create JSON RPC call object to ${url}` ); + const s = findPowNumber( + "\"cid\":1000,\"uri\":\"geth://\",\"jsps\":[\"/result\"]," + + "\"post\":\"{\\\"jsonrpc\\\":\\\"2.0\\\"," + + "\\\"method\\\":\\\"eth_gasPrice\\\",\\\"params\\\":[],\\\"id\\\":1}\"", + details, isVerbose ); + const joIn: any = { method: "oracle_submitRequest", params: [ s ] }; + if( isVerboseTraceDetails ) + details.debug( "RPC call {} is {}", "oracle_submitRequest", joIn ); + const joOut: any = await joCall.call( joIn ); + gp = await handleOracleSubmitRequestResult( + oracleOpts, details, isVerboseTraceDetails, joCall, joIn, joOut ); + await joCall.disconnect(); + if( gp ) + return gp; + } catch ( err ) { + details.error( "ORACLE RPC call problem for URL {url}, error description: {err}", + url, err ); + if( joCall ) + await joCall.disconnect(); + throw new Error( `ORACLE ERROR: RPC connection problem for url ${url}, ` + + `error description: ${owaspUtils.extractErrorMessage( err )}` ); + } +} diff --git a/src/owaspUtils.ts b/src/owaspUtils.ts new file mode 100644 index 00000000..38039064 --- /dev/null +++ b/src/owaspUtils.ts @@ -0,0 +1,833 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file owaspUtils.ts + * @copyright SKALE Labs 2019-Present + */ + +// introduction: https://github.com/Checkmarx/JS-SCP +// main PDF with rules to follow: +// https://www.gitbook.com/download/pdf/book/checkmarx/JS-SCP +// top 10 hit parade: https://owasp.org/www-project-top-ten/ + +import * as log from "./log.js"; +import * as ethersMod from "ethers"; +import * as fs from "fs"; + +export interface TXYSignature { + X: string + Y: string +} + +export interface TBLSSignature { + blsSignature: any[2] // BLS glue of signatures, X then Y + hashA: string // G1.X from joGlueResult.hashSrc + hashB: string // G1.Y from joGlueResult.hashSrc + counter: string | number | null +} + +const BigNumber = ethersMod.ethers.BigNumber; + +const safeURL = log.safeURL; +const replaceAll = log.replaceAll; +const extractErrorMessage = log.extractErrorMessage; + +export { ethersMod, safeURL, replaceAll, extractErrorMessage, BigNumber }; + +export function rxIsInt( val: any ): boolean { + try { + const intRegex = /^-?\d+$/; + if( !intRegex.test( val ) ) + return false; + const intVal = parseInt( val, 10 ); + if( parseFloat( val ) == intVal && ( !isNaN( intVal ) ) ) + return true; + } catch ( err ) { + } + return false; +} + +export function rxIsFloat( val: any ): boolean { + try { + const floatRegex = /^-?\d+(?:[.,]\d*?)?$/; + if( !floatRegex.test( val ) ) + return false; + val = parseFloat( val ); + if( isNaN( val ) ) + return false; + return true; + } catch ( err ) { + } + return false; +} + +export function parseIntOrHex( s: any ): number { + if( typeof s !== "string" ) + return parseInt( s ); + s = s.trim(); + if( s.length > 2 && s[0] == "0" && ( s[1] == "x" || s[1] == "X" ) ) + return parseInt( s, 16 ); + return parseInt( s, 10 ); +} + +export function validateRadix( value: any, radix?: any ): boolean { + value = ( value ? value.toString() : "10" ).trim(); + radix = ( radix === null || radix === undefined ) + ? ( ( value.length > 2 && value[0] == "0" && ( value[1] == "x" || value[1] == "X" ) ) + ? 16 + : 10 ) + : parseInt( radix, 10 ); + return radix; +} + +export function validateInteger( value: any, radix?: any ): boolean { + try { + if( value === null || value === undefined ) + return false; + if( value === 0 || value === 0.0 ) + return true; + const s = value ? value.toString().trim() : ""; + if( s.length < 1 ) + return false; + radix = validateRadix( value, radix ); + if( ( !isNaN( value ) ) && + ( parseInt( value, radix ) == value || radix !== 10 ) && + ( !isNaN( parseInt( value, radix ) ) ) + ) + return true; + } catch ( err ) { + } + return false; +} + +export function toInteger( value: any, radix?: any ): number { + try { + if( value === 0 || value === 0.0 || value === null || value === undefined ) + return 0; + value = ( value ? value.toString().trim() : "" ).trim(); + radix = validateRadix( value, radix ); + if( !validateInteger( value, radix ) ) + return NaN; + return parseInt( value.toString().trim(), radix ); + } catch ( err ) { + } + return 0; +} + +export function validateFloat( value: any ): boolean { + try { + if( value === null || value === undefined ) + return false; + if( value === 0 || value === 0.0 ) + return true; + const f = parseFloat( value.toString().trim() ); + if( isNaN( f ) ) + return false; + return true; + } catch ( err ) { + } + return false; +} + +export function toFloat( value: any ): number { + try { + if( value === 0 || value === 0.0 || value === null || value === undefined ) + return 0.0; + const f = parseFloat( value.toString().trim() ); + return f; + } catch ( err ) { + } + return 0.0; +} + +export function validateURL( s: any ): boolean { + const url = toURL( s ); + if( url == null ) + return false; + return true; +} + +export function toURL( s: any ): URL | null { + try { + if( s == null || s == undefined ) + return null; + if( typeof s !== "string" ) + return null; + s = s.trim(); + if( s.length <= 0 ) + return null; + const sc = s[0]; + if( sc == "\"" || sc == "'" ) { + const cnt = s.length; + if( s[cnt - 1] == sc ) { + const ss = s.substring( 1, cnt - 1 ); + const objURL = toURL( ss ); + if( objURL != null && objURL != undefined ) { + const anyURL: any = objURL; + anyURL.strStrippedStringComma = sc; + } + return objURL; + } + return null; + } + const objURL = new URL( s ); + if( !objURL.hostname ) + return null; + if( objURL.hostname.length === 0 ) + return null; + const anyURL: any = objURL; + anyURL.strStrippedStringComma = null; + return objURL; + } catch ( err ) { + return null; + } +} + +export function toStringURL( s?: any, defValue?: string ): string { + defValue = defValue ?? ""; + try { + const url = toURL( s ); + if( url == null || url == undefined ) + return defValue; + return url.toString(); + } catch ( err ) { + return defValue; + } +} + +export function isUrlHTTP( strURL?: any ): boolean { + try { + if( !validateURL( strURL ) ) + return false; + const url = new URL( strURL ); + if( url.protocol == "http:" || url.protocol == "https:" ) + return true; + } catch ( err ) { + } + return false; +} + +export function isUrlWS( strURL?: any ): boolean { + try { + if( !validateURL( strURL ) ) + return false; + const url = new URL( strURL ); + if( url.protocol == "ws:" || url.protocol == "wss:" ) + return true; + } catch ( err ) { + } + return false; +} + +export function toBoolean( value?: any ): boolean { + let b = false; + try { + if( value === null || value === undefined ) + return false; + if( typeof value === "boolean" ) + return value; + if( typeof value === "string" ) { + const ch = value[0].toLowerCase(); + if( ch == "y" || ch == "t" ) + b = true; else if( validateInteger( value ) ) + b = !!toInteger( value ); else if( validateFloat( value ) ) + b = !!toFloat( value ); else + b = !!b; + } else + b = !!b; + } catch ( err ) { + b = false; + } + b = !!b; + return b; +} + +export function validateInputAddresses( address?: any ): boolean { + return ( /^(0x){1}[0-9a-fA-F]{40}$/i.test( address ) ); +} + +export function validateEthAddress( value?: any ): boolean { + try { + if( validateInputAddresses( ensureStartsWith0x( value ) ) ) + return true; + } catch ( err ) { + } + return false; +} + +export function validateEthPrivateKey( value?: any ): boolean { + try { + const ethersWallet = new ethersMod.ethers.Wallet( ensureStartsWith0x( value ) ); + if( ethersWallet.address ) + return true; + } catch ( err ) { + } + return false; +} + +export function toEthAddress( value?: any, defValue?: string ): string { + try { + value = value ? ensureStartsWith0x( value.toString() ) : ""; + defValue = defValue ?? ""; + if( !validateEthAddress( value ) ) + return defValue; + } catch ( err ) { + } + return value; +} + +export function toEthPrivateKey( value?: any, defValue?: string ): string { + try { + value = value ? value.toString() : ""; + defValue = defValue ?? ""; + if( !validateEthPrivateKey( value ) ) + return defValue; + } catch ( err ) { + } + return value; +} + +export function verifyArgumentWithNonEmptyValue( joArg?: any ): any { + if( ( !joArg.value ) || ( typeof joArg.value === "string" && joArg.value.length === 0 ) ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must not be empty", + joArg.value, joArg.name ) ); + process.exit( 126 ); + } + return joArg; +} + +export function verifyArgumentIsURL( joArg?: any ): any { + try { + verifyArgumentWithNonEmptyValue( joArg ); + const url = toURL( joArg.value ); + if( url == null ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be valid URL", + joArg.value, joArg.name ) ); + process.exit( 126 ); + } + if( url.hostname.length <= 0 ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be valid URL", + joArg.value, joArg.name ) ); + process.exit( 126 ); + } + return joArg; + } catch ( err ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be valid URL", + joArg.value, joArg.name ) ); + process.exit( 126 ); + } +} + +export function verifyArgumentIsInteger( joArg?: any ): any { + try { + verifyArgumentWithNonEmptyValue( joArg ); + if( !validateInteger( joArg.value ) ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be valid integer", + joArg.value, joArg.name ) ); + process.exit( 126 ); + } + joArg.value = toInteger( joArg.value ); + return joArg; + } catch ( err ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be valid integer", + joArg.value, joArg.name ) ); + process.exit( 126 ); + } +} + +export function verifyArgumentIsIntegerIpPortNumber( joArg: any ): any { + try { + verifyArgumentIsInteger( joArg ); + if( joArg.value < 0 ) + throw new Error( `Port number ${joArg.value} cannot be negative` ); + if( joArg.value < 1 ) + throw new Error( `Port number ${joArg.value} too small` ); + if( joArg.value > 65535 ) + throw new Error( `Port number ${joArg.value} too big` ); + return joArg; + } catch ( err ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be " + + "valid integer IP port number", joArg.value, joArg.name ) ); + process.exit( 126 ); + } +} + +export function verifyArgumentIsPathToExistingFile( joArg?: any ): any { + try { + verifyArgumentWithNonEmptyValue( joArg ); + const stats = fs.lstatSync( joArg.value ); + if( stats.isDirectory() ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be " + + "path to existing file, path to folder provided", joArg.value, joArg.name ) ); + process.exit( 126 ); + } + if( !stats.isFile() ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be " + + "path to existing file, bad path provided", joArg.value, joArg.name ) ); + process.exit( 126 ); + } + return joArg; + } catch ( err ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be " + + "path to existing file", joArg.value, joArg.name ) ); + process.exit( 126 ); + } +} + +export function verifyArgumentIsPathToExistingFolder( joArg?: any ): any { + try { + verifyArgumentWithNonEmptyValue( joArg ); + const stats = fs.lstatSync( joArg.value ); + if( stats.isFile() ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be " + + "path to existing folder, path to file provided", joArg.value, joArg.name ) ); + process.exit( 126 ); + } + if( !stats.isDirectory() ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument }{} must be " + + "path to existing folder, bad path provided", joArg.value, joArg.name ) ); + process.exit( 126 ); + } + return joArg; + } catch ( err ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be " + + "path to existing folder", joArg.value, joArg.name ) ); + process.exit( 126 ); + } +} + +export function verifyArgumentIsArrayOfIntegers( joArg?: any ): any[] { + try { + verifyArgumentWithNonEmptyValue( joArg ); + if( joArg.value.length < 3 ) { + console.log( log.fmtFatal( "(OWASP) Length {} of argument {} must be " + + "bigger than 2", joArg.value.length, joArg.name ) ); + process.exit( 126 ); + } + if( joArg.value[0] !== "[" || joArg.value[joArg.value.length - 1] !== "]" ) { + console.log( log.fmtFatal( "(OWASP) First and last symbol {} of argument {} must be " + + "brackets", joArg.value, joArg.name ) ); + process.exit( 126 ); + } + const newValue = joArg.value.replace( "[", "" ).replace( "]", "" ).split( "," ); + for( let index = 0; index < newValue.length; index++ ) { + if( !newValue[index] || + ( typeof newValue[index] === "string" && newValue[index].length === 0 ) + ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must not be empty", + newValue[index], joArg.name ) ); + process.exit( 126 ); + } + if( !validateInteger( newValue[index] ) ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be valid integer", + newValue[index], joArg.name ) ); + process.exit( 126 ); + } + newValue[index] = toInteger( newValue[index] ); + } + return newValue; + } catch ( err ) { + console.log( log.fmtFatal( "(OWASP) Value {} of argument {} must be valid integer array", + joArg.value, joArg.name ) ); + process.exit( 126 ); + } +} + +export function ensureStartsWith0x( s?: any ): string { + if( s == null || s == undefined || typeof s !== "string" ) + return s; + if( s.length < 2 ) + return "0x" + s; + if( s[0] == "0" && s[1] == "x" ) + return s; + return "0x" + s; +} + +export function removeStarting0x( s?: any ): string { + if( s == null || s == undefined || typeof s !== "string" ) + return s; + if( s.length < 2 ) + return s; + if( s[0] == "0" && s[1] == "x" ) + return s.substr( 2 ); + return s; +} + +export function inetNtoA( n: number ): string { + const a = ( ( n >> 24 ) & 0xFF ) >>> 0; + const b = ( ( n >> 16 ) & 0xFF ) >>> 0; + const c = ( ( n >> 8 ) & 0xFF ) >>> 0; + const d = ( n & 0xFF ) >>> 0; + return ( a + "." + b + "." + c + "." + d ); +} + +export function ipFromHex( hex?: any ): string { + return inetNtoA( parseInt( removeStarting0x( hex ), 16 ) ); +} + +export function cloneObjectByRootKeys( joIn?: any ): any { + const joOut: any = { }; const arrKeys = Object.keys( joIn ); + for( let i = 0; i < arrKeys.length; ++i ) { + const key = arrKeys[i]; + const value = joIn[key]; + joOut[key] = value; + } + return joOut; +} + +// example: "1ether" -> "1000000000000000000" +// supported suffix aliases, lowercase +const gMapMoneyNameSuffixAliases: any = { + ethe: "ether", + ethr: "ether", + eth: "ether", + eter: "ether", + ete: "ether", + et: "ether", + eh: "ether", + er: "ether", + finne: "finney", + finn: "finney", + fin: "finney", + fn: "finney", + fi: "finney", + szab: "szabo", + szb: "szabo", + sza: "szabo", + sz: "szabo", + shanno: "shannon", + shannn: "shannon", + shann: "shannon", + shan: "shannon", + sha: "shannon", + shn: "shannon", + sh: "shannon", + lovelac: "lovelace", + lovela: "lovelace", + lovel: "lovelace", + love: "lovelace", + lovl: "lovelace", + lvl: "lovelace", + lvla: "lovelace", + lvlc: "lovelace", + lvc: "lovelace", + lv: "lovelace", + lo: "lovelace", + lc: "lovelace", + ll: "lovelace", + babbag: "babbage", + babba: "babbage", + babbg: "babbage", + babb: "babbage", + bab: "babbage", + bag: "babbage", + bbb: "babbage", + bb: "babbage", + bg: "babbage", + ba: "babbage", + be: "babbage", + we: "wei", + wi: "wei", + + // next are advanced kind of + noether: "noether", + noeth: "noether", + kwei: "kwei", + femtoether: "femtoether", + femto: "femtoether", + mwei: "mwei", + picoether: "picoether", + pico: "picoether", + gwei: "gwei", + nanoether: "nanoether", + nano: "nanoether", + microether: "microether", + micro: "microether", + milliether: "milliether", + milli: "milliether", + kether: "kether", + mether: "mether", + gether: "gether", + tether: "tether" +}; + +export function parseMoneyUnitName( s: string ): string { + s = s.trim().toLowerCase(); + if( s == "" ) + return "wei"; + if( s in gMapMoneyNameSuffixAliases ) { + s = gMapMoneyNameSuffixAliases[s]; + return s; + } + return s; +} + +function moneyUnitNameToEthersParseSpec( s: string ): string | number { + switch ( s.toString().trim().toLowerCase() ) { + case "shannon": return 9; + case "lovelace": return 6; + case "babbage": return 3; + case "femtoether": return "ether"; + case "picoether": return "ether"; + case "nanoether": return "ether"; + case "microether": return "ether"; + case "milliether": return "ether"; + case "kether": return "ether"; + case "mether": return "ether"; + case "gether": return "ether"; + case "tether": return "ether"; + } + return s; +} + +function moneyUnitNameToPostDivider( s: string ): string | null { + switch ( s.toString().trim().toLowerCase() ) { + case "femtoether": return "1000000000000000"; + case "picoether": return "1000000000000"; + case "nanoether": return "1000000000"; + case "microether": return "1000000"; + case "milliether": return "1000"; + } + return null; +} +function moneyUnitNameToPostMultiplier( s: string ): string | null { + switch ( s.toString().trim().toLowerCase() ) { + case "kether": return "1000"; + case "mether": return "1000000"; + case "gether": return "1000000000"; + case "tether": return "1000000000000"; + } + return null; +} + +export function parseMoneySpecToWei( s?: any, isThrowException?: boolean ): string { + try { + isThrowException = ( !!isThrowException ); + if( s == null || s == undefined ) { + if( isThrowException ) + throw new Error( "no parse-able value provided" ); + return "0"; + } + s = s.toString().trim(); + let strNumber = ""; + while( s.length > 0 ) { + const chr = s[0]; + if( /^\d+$/.test( chr ) || // is numeric + chr == "." + ) { + strNumber += chr; + s = s.substr( 1 ); // remove first character + continue; + } + if( chr == " " || chr == "\t" || chr == "\r" || chr == "\n" ) + s = s.substr( 1 ); // remove first character + s = s.trim().toLowerCase(); + break; + } + // here s is rest suffix string, number is number as string or empty string + if( strNumber == "" ) + throw new Error( "no number or float value found" ); + s = parseMoneyUnitName( s ); + const ddr = moneyUnitNameToPostDivider( s ); + const mlr = moneyUnitNameToPostMultiplier( s ); + s = moneyUnitNameToEthersParseSpec( s ); + s = ethersMod.ethers.utils.parseUnits( strNumber, s ); + if( ddr != null ) + s = s.div( toBN( ddr ) ); + if( mlr != null ) + s = s.mul( toBN( mlr ) ); + s = s.toString(); + return s; + } catch ( err: any ) { + if( isThrowException ) { + throw new Error( `Parse error in parseMoneySpecToWei( ${s} ), ` + + `error is: ${err}` ); + } + } + return "0"; +} + +export function computeChainIdFromSChainName( strName: string ): string { + let h = ethersMod.ethers.utils.id( strName ); + h = removeStarting0x( h ).toLowerCase(); + while( h.length < 64 ) + h = "0" + h; + h = h.substr( 0, 14 ); + return ensureStartsWith0x( h ); +} + +export function privateKeyToAccountAddress( keyPrivate: string ): string { + return ethersMod.ethers.utils.computeAddress( + ensureStartsWith0x( keyPrivate ) ); +} + +export function fnAddressImpl_( anyThis: any ): string { + if( anyThis.address_ == undefined || anyThis.address_ == null || anyThis.address_ == "" ) { + if( anyThis.privateKey ) + anyThis.address_ = privateKeyToAccountAddress( anyThis.privateKey ).toString(); + } + return anyThis.address_; +} + +export function getEthersProviderFromURL( + strURL: URL | string ): ethersMod.ethers.providers.JsonRpcProvider { + const url = new URL( strURL.toString() ); + let userName: string | null = null; let userPwd: string | null = null; + if( url.username ) { + userName = url.username; + userPwd = url.password; + url.username = ""; + url.password = ""; + strURL = url.href; // remove credentials + } + const joConnectionInfo: any = { // see https://docs.ethers.io/v5/api/utils/web/#ConnectionInfo + url: strURL, + allowInsecureAuthentication: true + }; + if( userName ) { + joConnectionInfo.user = userName; + if( userPwd ) + joConnectionInfo.password = userPwd; + } + const ethersProvider: ethersMod.ethers.providers.JsonRpcProvider = + new ethersMod.ethers.providers.JsonRpcProvider( joConnectionInfo ); + return ethersProvider; +} + +export function ethersProviderToUrl( + ethersProvider: ethersMod.ethers.providers.JsonRpcProvider | null +): string { + let strURL: string | null = null; + if( ethersProvider && + "connection" in ethersProvider && typeof ethersProvider.connection === "object" && + "url" in ethersProvider.connection && typeof ethersProvider.connection.url === "string" + ) + strURL = ethersProvider.connection.url.toString(); + return strURL ?? "N/A-URL"; +} + +export function isHexPrefixed( s: any ): boolean { + if( typeof s !== "string" ) { + throw new Error( + "Parameter value of owaspUtils.isHexPrefixed() must be type 'string' but it's " + + `type ${typeof s}, while checking isHexPrefixed.` ); + } + return ( s.slice( 0, 2 ) === "0x" ); +} + +export function stripHexPrefix( s: any ): string { + if( typeof s !== "string" ) + return s; + return isHexPrefixed( s ) ? s.slice( 2 ) : s; +} + +export function toBNbasic( x?: any, optionalRadix?: number ): any { + try { + if( optionalRadix && typeof optionalRadix === "number" && optionalRadix == 16 ) + x = ensureStartsWith0x( x ); + const bn = ethersMod.ethers.BigNumber.from( x ); + return bn; + } catch ( err: any ) { + console.log( `CRITICAL ERROR: Failure in owaspUtils.toBNbasic( ${x} ): ${err}` ); + throw err; + } +} + +export function toBN( arg: any ): any { + if( typeof arg === "string" || typeof arg === "number" ) { + let multiplier = toBNbasic( 1 ); + const formattedString = String( arg ).toLowerCase().trim(); + const isHexPrefixed = + formattedString.substr( 0, 2 ) === "0x" || + formattedString.substr( 0, 3 ) === "-0x"; + let stringArg = stripHexPrefix( formattedString ); + if( stringArg.substr( 0, 1 ) === "-" ) { + stringArg = stripHexPrefix( stringArg.slice( 1 ) ); + multiplier = toBNbasic( -1, 10 ); + } + stringArg = stringArg === "" ? "0" : stringArg; + const isMatchN: boolean = !!stringArg.match( /^-?[0-9]+$/ ); + const isMatchX: boolean = !!stringArg.match( /^[0-9A-Fa-f]+$/ ); + const isMatchA: boolean = !!stringArg.match( /^[a-fA-F]+$/ ); + if( ( ( !isMatchN ) && isMatchX ) || isMatchA || ( isHexPrefixed && isMatchX ) ) + return toBNbasic( stringArg, 16 ).mul( multiplier ); + if( ( isMatchN || stringArg === "" ) && ( !isHexPrefixed ) ) + return toBNbasic( stringArg, 10 ).mul( multiplier ); + } else if( typeof arg === "object" && arg.toString && ( !arg.pop && !arg.push ) ) { + if( arg.toString().match( /^-?[0-9]+$/ ) && ( arg.mul || arg.dividedToIntegerBy ) ) + return toBNbasic( arg.toString(), 10 ); + } else if( arg ) + return toBNbasic( arg ); // try to convert as is + + throw new Error( + "Error in owaspUtils.toBN() while converting " + + `number ${JSON.stringify( arg )}to BN.js instance, error: ` + + "invalid number value. Value must be an integer, hex string, BN or BigNumber instance. " + + "Note, decimals are not supported." ); +} + +export function isNumeric( s?: any ): boolean { + return /^\d+$/.test( s ); +} + +export function toHexStringSafe( val?: any ): string { + if( !val ) + return "0x0"; + if( "toHexString" in val && typeof val.toHexString === "function" ) + return val.toHexString(); + if( typeof val === "number" || typeof val === "bigint" ) + return ensureStartsWith0x( val.toString( 16 ) ); + if( "toString" in val && typeof val.toString === "function" ) + return val.toString(); + return val.toString(); +} + +export function setInterval2( fn: () => void, t: number, stepMilliSeconds?: number ): any { + const iv: any = { + real_iv: null, + stepMilliSeconds: stepMilliSeconds ?? 1000, + maxMilliSeconds: t, + accumulatedMilliSeconds: 0 + }; + iv.real_iv = setInterval( () => { + iv.accumulatedMilliSeconds += iv.stepMilliSeconds; + if( iv.accumulatedMilliSeconds >= iv.maxMilliSeconds ) { + iv.accumulatedMilliSeconds = 0; + fn(); + } + }, iv.stepMilliSeconds ); + return iv; +} + +export function clearInterval2( iv: any ): void { + if( !iv ) + return; + if( !( "real_iv" in iv ) ) + return; + if( !iv.real_iv ) + return; + clearInterval( iv.real_iv ); + iv.real_iv = null; +} + +export function escapeShell( cmd: string ): string { + return "\"" + cmd.replace( /(["'$`\\])/g, "\\$1" ) + "\""; +} diff --git a/src/package.json b/src/package.json new file mode 100644 index 00000000..84776503 --- /dev/null +++ b/src/package.json @@ -0,0 +1,56 @@ +{ + "name": "skale-ima-agent-app", + "license": "AGPL-3.0", + "author": "SKALE Labs and contributors", + "type": "module", + "scripts": { + "build": "./node_modules/typescript/bin/tsc", + "rebuild": "yarn clean-build && yarn build", + "clean-build": "rm -rf ./src/build/* || true", + "lint-check": "eslint -c .eslintrc.cjs ./*.ts", + "lint-fix": "eslint -c .eslintrc.cjs ./*.ts --fix", + "check-outdated": "yarn outdated", + "upgrade-to-latest": "yarn upgrade --latest" + }, + "dependencies": { + "ethers": "^5.7.2", + "@ethersproject/experimental": "^5.7.0", + "solc": "0.8.6", + "uuid": "^9.0.0", + "@types/uuid": "^9.0.7", + "express": "^4.18.2", + "@types/express": "^4.17.21", + "body-parser": "^1.20.1", + "@types/body-parser": "^1.19.5", + "jayson": "^4.0.0", + "ws": "^8.15.0 ", + "@types/ws": "^8.5.10", + "urllib": "^3.21.0", + "sha3": "2.1.4", + "serve-static": "^1.15.0", + "shelljs": "^0.8.5", + "@types/shelljs": "^0.8.15", + "connect": "^3.7.0", + "ethereum-multicall": "^2.16.1", + "ioredis": "^5.0.0", + "@types/ioredis": "^4.28.2", + "ethereumjs-util": "^7.1.5", + "ethereumjs-wallet": "^1.0.2" + }, + "devDependencies": { + "eslint": "^8.53.0", + "eslint-config-standard-with-typescript": "^39.1.1", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-standard": "^4.0.1", + "eslint-plugin-n": "^16.3.0", + "typescript": "^5.3.3", + "@typescript-eslint/eslint-plugin": "^6.14.0" + }, + "resolutions": { + } +} + + diff --git a/src/pow b/src/pow new file mode 100755 index 00000000..cff13c5e Binary files /dev/null and b/src/pow differ diff --git a/src/pwa.ts b/src/pwa.ts new file mode 100644 index 00000000..5355df0a --- /dev/null +++ b/src/pwa.ts @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file pwa.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as rpcCall from "./rpcCall.js"; +import * as imaBLS from "./bls.js"; +import * as imaUtils from "./utils.js"; +import type * as state from "./state.js"; +import type * as discoveryTools from "./discoveryTools.js"; +import * as owaspUtils from "./owaspUtils.js"; + +function computeWalkNodeIndices( nNodeNumber: number, nNodesCount: number ): number[] { + if( nNodeNumber === null || nNodeNumber === undefined || + nNodesCount === undefined ) + return []; + if( nNodesCount <= 1 ) + return []; // PWA is N/A + if( !( nNodeNumber >= 0 && nNodeNumber < nNodesCount ) ) + return []; // PWA is N/A + let i = nNodeNumber - 1; + if( i < 0 ) + i = nNodesCount - 1; + const arrWalkNodeIndices: number[] = []; + for( ; true; ) { + if( i == nNodeNumber ) + break; + arrWalkNodeIndices.push( i ); + --i; + if( i < 0 ) + i = nNodesCount - 1; + } + return arrWalkNodeIndices; +} + +export function checkLoopWorkTypeStringIsCorrect( strLoopWorkType: string ): boolean { + if( !strLoopWorkType ) + return false; + switch ( strLoopWorkType.toString().toLowerCase() ) { + case "oracle": + case "m2s": + case "s2m": + case "s2s": + return true; + } + return false; +} + +function composeEmptyStateForPendingWorkAnalysis(): any { + return { + oracle: { isInProgress: false, ts: 0 }, + m2s: { isInProgress: false, ts: 0 }, + s2m: { isInProgress: false, ts: 0 }, + s2s: { mapS2S: { } } + }; +} + +function getNodeProgressAndTimestamp( + joNode: discoveryTools.TSChainNode, strLoopWorkType: string, nIndexS2S: number ): any { + if( !( "pwaState" in joNode ) ) + joNode.pwaState = composeEmptyStateForPendingWorkAnalysis(); + strLoopWorkType = strLoopWorkType.toLowerCase(); + if( ( !joNode.pwaState ) || ( !( strLoopWorkType in joNode.pwaState ) ) ) { + throw new Error( `Specified value ${strLoopWorkType} is not a correct loop work type, ` + + "cannot access info" ); + } + if( strLoopWorkType != "s2s" ) + return ( joNode.pwaState as any )[strLoopWorkType]; + if( !( nIndexS2S in joNode.pwaState[strLoopWorkType].mapS2S ) ) + joNode.pwaState[strLoopWorkType].mapS2S[nIndexS2S] = { isInProgress: false, ts: 0 }; + + return joNode.pwaState[strLoopWorkType].mapS2S[nIndexS2S]; +} + +export async function checkOnLoopStart( + imaState: state.TIMAState, strLoopWorkType: string, nIndexS2S?: number +): Promise { + try { + nIndexS2S = nIndexS2S ?? 0; // convert to number if undefined + if( !checkLoopWorkTypeStringIsCorrect( strLoopWorkType ) ) + throw new Error( `Specified value ${strLoopWorkType} is not a correct loop work type` ); + if( !imaState.isPWA ) + return true; // PWA is N/A + if( imaState.nNodesCount <= 1 ) + return true; // PWA is N/A + if( !( imaState.nNodeNumber >= 0 && imaState.nNodeNumber < imaState.nNodesCount ) ) + return true; // PWA is N/A + if( !imaState.joSChainNetworkInfo ) + return true; // PWA is N/A + const jarrNodes = imaState.joSChainNetworkInfo.network; + if( !jarrNodes ) + throw new Error( "S-Chain network info is not available yet to PWA" ); + const arrBusyNodeIndices: number[] = []; + const arrWalkNodeIndices: number[] = + computeWalkNodeIndices( imaState.nNodeNumber, imaState.nNodesCount ); + if( imaState.isPrintPWA ) { + log.debug( "PWA will check loop start condition via node(s) sequence {}...", + arrBusyNodeIndices ); + } + const nUtcUnixTimeStamp = Math.floor( ( new Date() ).getTime() / 1000 ); + for( let i = 0; i < arrWalkNodeIndices.length; ++i ) { + const walkNodeIndex = arrWalkNodeIndices[i]; + const joNode = jarrNodes[walkNodeIndex]; + const joProps: any = getNodeProgressAndTimestamp( joNode, strLoopWorkType, nIndexS2S ); + if( joProps && typeof joProps === "object" && + "isInProgress" in joProps && joProps.isInProgress && + joProps.ts != 0 && nUtcUnixTimeStamp >= joProps.ts + ) { + const d = nUtcUnixTimeStamp - joProps.ts; + if( d >= imaState.nTimeoutSecondsPWA ) { + if( imaState.isPrintPWA ) { + log.warning( + "PWA busy state timeout for node #{}, old timestamp is {}, current " + + "system timestamp is {}, duration {} is greater than conditionally " + + "allowed {} and exceeded by {} second(s)", walkNodeIndex, joProps.ts, + nUtcUnixTimeStamp, d, imaState.nTimeoutSecondsPWA, + d - imaState.nTimeoutSecondsPWA ); + }; + joProps.isInProgress = false; + joProps.ts = 0; + continue; + } + arrBusyNodeIndices.push( walkNodeIndex ); + } + } + if( arrBusyNodeIndices.length > 0 ) { + if( imaState.isPrintPWA ) { + log.error( "PWA loop start condition check failed, busy node(s): {}", + arrBusyNodeIndices ); + } + return false; + } + if( imaState.isPrintPWA ) + log.success( "PWA loop start condition check passed" ); + } catch ( err ) { + log.critical( "Exception in PWA check on loop start: {err}, stack is:\n{stack}", + err, err ); + } + return true; +} + +export async function handleLoopStateArrived( + imaState: state.TIMAState, nNodeNumber: number, strLoopWorkType: string, nIndexS2S: number, + isStart: boolean, ts: any, signature: any +): Promise { + const se = isStart ? "start" : "end"; + let isSuccess = false; + let joNode: any = null; + try { + if( !checkLoopWorkTypeStringIsCorrect( strLoopWorkType ) ) + throw new Error( `Specified value ${strLoopWorkType} is not a correct loop work type` ); + if( !imaState.isPWA ) + return true; + if( imaState.nNodesCount <= 1 ) + return true; // PWA is N/A + if( !( imaState.nNodeNumber >= 0 && imaState.nNodeNumber < imaState.nNodesCount ) ) + return true; // PWA is N/A + if( !imaState.joSChainNetworkInfo ) + return true; // PWA is N/A + const jarrNodes = imaState.joSChainNetworkInfo.network; + if( !jarrNodes ) + throw new Error( "S-Chain network info is not available yet to PWA" ); + joNode = jarrNodes[nNodeNumber]; + const joProps: any = getNodeProgressAndTimestamp( joNode, strLoopWorkType, nIndexS2S ); + if( imaState.isPrintPWA ) { + log.trace( "PWA loop-{} state arrived for node {}, PWA state {}, arrived " + + "signature is {}", se, nNodeNumber, joNode.pwaState, signature ); + } + const strMessageHash = imaBLS.keccak256ForPendingWorkAnalysis( + nNodeNumber, strLoopWorkType, isStart, owaspUtils.toInteger( ts ) ); + const isSignatureOK = await imaBLS.doVerifyReadyHash( + strMessageHash, nNodeNumber, signature, imaState.isPrintPWA ); + if( !isSignatureOK ) + throw new Error( "BLS verification failed" ); + joProps.isInProgress = ( !!isStart ); + joProps.ts = owaspUtils.toInteger( ts ); + if( imaState.isPrintPWA ) { + log.success( + "PWA loop-{} state successfully verified for node {}, now have PWA state {}, " + + "arrived signature is {}", se, nNodeNumber, joNode.pwaState, signature ); + } + isSuccess = true; + } catch ( err ) { + isSuccess = false; + log.critical( + "Exception in PWA handler for loop-{} for node {}, PWA state {}, arrived signature " + + "is {}, error is: {err}, stack is:\n{stack}", se, nNodeNumber, + ( joNode && "pwaState" in joNode ) ? joNode.pwaState : "N/A", signature, + err, err ); + } + return isSuccess; +} + +async function notifyOnLoopImpl( + imaState: state.TIMAState, strLoopWorkType: string, isStart: boolean, nIndexS2S?: number +): Promise { + const se = isStart ? "start" : "end"; + try { + nIndexS2S = nIndexS2S ?? 0; // convert to number if undefined + if( !checkLoopWorkTypeStringIsCorrect( strLoopWorkType ) ) + throw new Error( `Specified value ${strLoopWorkType} is not a correct loop work type` ); + if( !imaState.isPWA ) + return true; + if( imaState.nNodesCount <= 1 ) + return true; // PWA is N/A + if( !( imaState.nNodeNumber >= 0 && imaState.nNodeNumber < imaState.nNodesCount ) ) + return true; // PWA is N/A + if( !imaState.joSChainNetworkInfo ) + return true; // PWA is N/A + const jarrNodes = imaState.joSChainNetworkInfo.network; + if( !jarrNodes ) + throw new Error( "S-Chain network info is not available yet to PWA" ); + const nUtcUnixTimeStamp = Math.floor( ( new Date() ).getTime() / 1000 ); + + const strMessageHash = imaBLS.keccak256ForPendingWorkAnalysis( + owaspUtils.toInteger( imaState.nNodeNumber ), + strLoopWorkType, isStart, nUtcUnixTimeStamp ); + const signature = await imaBLS.doSignReadyHash( strMessageHash, imaState.isPrintPWA ); + await handleLoopStateArrived( + imaState, imaState.nNodeNumber, strLoopWorkType, + nIndexS2S, isStart, nUtcUnixTimeStamp, signature + ); // save own started + for( let i = 0; i < jarrNodes.length; ++i ) { + const isThisNode = ( i == imaState.nNodeNumber ); + if( isThisNode ) + continue; // skip this node + const joNode = jarrNodes[i]; + const strNodeURL = imaUtils.composeImaAgentNodeUrl( joNode, isThisNode ); + const rpcCallOpts: rpcCall.TRPCCallOpts | null = null; + const joCall = + await rpcCall.create( strNodeURL, rpcCallOpts ) + .catch( async function( err: Error | string ): Promise { + log.error( + "PWA failed to perform] loop-{} notification RPC call to node " + + "#{} with URL {url}, error is: {err}", + se, i, strNodeURL, err ); + if( joCall ) + await joCall.disconnect(); + } ); + if( !joCall ) + return false; + const joIn: any = { + method: "skale_imaNotifyLoopWork", + params: { + nNodeNumber: owaspUtils.toInteger( imaState.nNodeNumber ), + strLoopWorkType: strLoopWorkType.toString(), + nIndexS2S: owaspUtils.toInteger( nIndexS2S ), + isStart: ( !!isStart ), + ts: nUtcUnixTimeStamp, + signature + } + }; + await joCall.call( joIn ); // no return value needed here + if( imaState.isPrintPWA ) { + log.success( "Was successfully sent PWA loop-{} notification to " + + "node #{} with URL {url}", se, i, strNodeURL ); + } + await joCall.disconnect(); + } + } catch ( err ) { + log.error( "Exception in PWA notify on loop {}: {err}, stack is:\n{stack}", se, + err, err ); + } + return true; +} + +export async function notifyOnLoopStart( + imaState: state.TIMAState, strLoopWorkType: string, nIndexS2S?: number +): Promise { + return await notifyOnLoopImpl( imaState, strLoopWorkType, true, nIndexS2S ); +} + +export async function notifyOnLoopEnd( + imaState: state.TIMAState, strLoopWorkType: string, nIndexS2S?: number +): Promise { + return await notifyOnLoopImpl( imaState, strLoopWorkType, false, nIndexS2S ); +} diff --git a/src/rpcCall.ts b/src/rpcCall.ts new file mode 100644 index 00000000..ec8be354 --- /dev/null +++ b/src/rpcCall.ts @@ -0,0 +1,676 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file rpcCall.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as ws from "ws"; +import * as urllib from "urllib"; +import * as https from "https"; +import * as net from "net"; +import { validateURL, isUrlWS } from "./owaspUtils.js"; +import * as log from "./log.js"; + +export interface TRPCCallOpts { + cert?: string + key?: string + ca?: string + isAutoReconnect?: boolean +} + +export type TFunctionConnectionResultHandler = + ( joCall: TRPCCall, err: Error | string | null + ) => Promise < void >; + +export type TFunctionCallResultHandler = + ( joIn: any, joOut: any, err: Error | string | null + ) => Promise < void >; + +export type TFunctionReconnect = + ( fnAfter: TFunctionConnectionResultHandler ) => Promise < void >; +export type TFunctionReconnectIfNeeded = + ( fnAfter: TFunctionConnectionResultHandler ) => Promise < void >; +export type TFunctionCall = + ( joIn: any, fnAfter?: TFunctionCallResultHandler ) => Promise < any >; +export type TFunctionDisconnect = ( + fnAfter?: TFunctionConnectionResultHandler +) => Promise < void >; + +export type TCallID = string | number | bigint; +export interface TCallHandlerEntry { + joIn: any + fn: TFunctionCallResultHandler + out: any + iv?: NodeJS.Timeout | null +} +export type TMapPendingByCallID = Map < TCallID, TCallHandlerEntry >; + +export interface TRPCCall { + url: string + joRpcOptions: TRPCCallOpts | null + mapPendingByCallID: TMapPendingByCallID + wsConn: ws.WebSocket | null + isAutoReconnect: boolean + isDisconnectMode: boolean + reconnect: TFunctionReconnect + reconnectIfNeeded: TFunctionReconnectIfNeeded + call: TFunctionCall + disconnect: TFunctionDisconnect +} + +const gSecondsConnectionTimeout = 60; + +export type TFunctionWsStep = ( nStep: number ) => Promise < boolean >; +export type TFunctionWsDone = ( nStep: number ) => Promise < void >; + +export async function waitWebSocketIsOpen( + socket: ws.WebSocket, fnDone?: TFunctionWsDone, fnStep?: TFunctionWsStep +): Promise { + fnDone = fnDone ?? async function( nStep: number ) { }; + fnStep = fnStep ?? async function( nStep: number ) { return true; }; + let nStep = 0; + const promiseComplete = new Promise( function( resolve, reject ) { + let isInsideAsyncHandler = false; + const fnAsyncHandler = async function(): Promise < void > { + if( isInsideAsyncHandler ) + return; + isInsideAsyncHandler = true; + ++nStep; + if( socket.readyState === 1 ) { + // Notice, connection is made if we are here + clearInterval( iv ); + if( fnDone ) + await fnDone( nStep ); + resolve( true ); + } else { + if( fnStep ) { + const isContinue = await fnStep( nStep ); + if( !isContinue ) { + clearInterval( iv ); + reject( new Error( "web socket wait timeout by callback " + + `on step ${nStep}` ) ); + } + } + } + isInsideAsyncHandler = false; + }; + const iv = setInterval( function(): void { + if( isInsideAsyncHandler ) + return; + fnAsyncHandler().then( () => { } ).catch( () => { } ); + }, 1000 ); // 1 second + } ); + await promiseComplete; +} + +export async function doConnect( + joCall: TRPCCall, opts: TRPCCallOpts | null, fn?: TFunctionConnectionResultHandler +): Promise { + try { + if( !validateURL( joCall.url ) ) { + throw new Error( "JSON RPC CALLER cannot connect web socket " + + `to invalid URL: ${joCall.url}` ); + } + if( isUrlWS( joCall.url ) ) { + let strWsError: string = ""; + joCall.wsConn = new ws.WebSocket( joCall.url ); + joCall.wsConn.on( "open", function(): void { + if( fn ) + fn( joCall, null ).then( function(): void {} ).catch( function(): void {} ); + } ); + joCall.wsConn.on( "close", function(): void { + strWsError = + "web socket was closed, please check provided URL is valid and accessible"; + joCall.wsConn = null; + } ); + joCall.wsConn.on( "error", function( err: Error | string ) { + strWsError = err.toString() || "internal web socket error"; + log.error( "{url} web socket error: {err}", joCall.url, err ); + const wsConn = joCall.wsConn; + joCall.wsConn = null; + if( wsConn ) + wsConn.close(); + doReconnectWsStep( joCall, opts ) + .then( function(): void {} ).catch( function(): void {} ); + } ); + joCall.wsConn.on( "fail", function( err: Error | string ) { + strWsError = err.toString() || "internal web socket failure"; + log.error( "{url} web socket fail: {err}", joCall.url, err ); + const wsConn = joCall.wsConn; + joCall.wsConn = null; + if( wsConn ) + wsConn.close(); + doReconnectWsStep( joCall, opts ) + .then( function(): void {} ).catch( function(): void {} ); + } ); + joCall.wsConn.on( "message", function incoming( data: any ) { + const joOut = JSON.parse( data ); + if( joOut.id in joCall.mapPendingByCallID ) { + const entry = joCall.mapPendingByCallID.get( joOut.id ); + joCall.mapPendingByCallID.delete( joOut.id ); + if( entry ) { + if( entry.iv ) { + clearTimeout( entry.iv ); + entry.iv = null; + } + clearTimeout( entry.out ); + if( entry.fn ) { + entry.fn( entry.joIn, joOut, null ) + .then( function(): void {} ).catch( function(): void {} ); + } + } + } + } ); + await waitWebSocketIsOpen( joCall.wsConn, + async function( nStep: number ): Promise { // work done handler + }, + async function( nStep: number ): Promise { // step handler + if( strWsError && typeof strWsError === "string" && strWsError.length > 0 ) { + log.error( "{url} web socket wait error detected: {err}", + joCall.url, strWsError ); + return false; + } + if( nStep >= gSecondsConnectionTimeout ) { + strWsError = "wait timeout, web socket is connecting too long"; + log.error( "{url} web socket wait timeout detected", joCall.url ); + const wsConn = joCall.wsConn; + joCall.wsConn = null; + if( wsConn ) + wsConn.close(); + doReconnectWsStep( joCall, opts ) + .then( function(): void {} ).catch( function(): void {} ); + return false; // stop waiting + } + return true; // continue waiting + } ); + if( strWsError && typeof strWsError === "string" && strWsError.length > 0 ) { + const err = new Error( strWsError ); + if( fn ) + await fn( joCall, err ); + return joCall; + } + } + if( fn ) + await fn( joCall, null ); + } catch ( err ) { + joCall.wsConn = null; + if( fn ) + await fn( joCall, err as Error ); + } + return joCall; +} + +export async function doConnectIfNeeded( + joCall: TRPCCall, opts: TRPCCallOpts | null, fn: TFunctionConnectionResultHandler +): Promise { + try { + if( !validateURL( joCall.url ) ) { + throw new Error( "JSON RPC CALLER cannot connect web socket " + + `to invalid URL: ${joCall.url}` ); + } + if( isUrlWS( joCall.url ) && ( !joCall.wsConn ) ) { + await joCall.reconnect( fn ); + return joCall; + } + if( fn ) + await fn( joCall, null ); + } catch ( err ) { + if( fn ) + await fn( joCall, err as Error ); + } + return joCall; +} + +async function doReconnectWsStep( joCall: TRPCCall, opts: TRPCCallOpts | null, + fn?: TFunctionConnectionResultHandler ): Promise { + if( !joCall.isAutoReconnect ) + return; + if( joCall.isDisconnectMode ) + return; + doConnect( joCall, opts, + async function( joCall: TRPCCall, err: Error | string | null ): Promise { + if( err ) { + doReconnectWsStep( joCall, opts ) + .then( function(): void {} ).catch( function(): void {} ); + return; + } + if( fn ) + await fn( joCall, null ); + } ).then( function(): void {} ).catch( function(): void {} ); +} + +async function doDisconnect( + joCall: TRPCCall, fn?: TFunctionConnectionResultHandler ): Promise { + try { + joCall.isDisconnectMode = true; + const wsConn = joCall.wsConn ? joCall.wsConn : null; + joCall.wsConn = null; + if( wsConn ) + wsConn.close(); + joCall.isDisconnectMode = false; + try { + if( fn ) + await fn( joCall, null ); + } catch ( err ) { + } + } catch ( err ) { + if( fn ) + await fn( joCall, err as Error ); + } +} + +export async function doCall( + joCall: TRPCCall, joIn: any, fn: TFunctionCallResultHandler ): Promise { + joIn = enrichTopLevelFieldsInJSON( joIn ); + if( joCall.wsConn ) { + const entry: TCallHandlerEntry = { + joIn, + fn, + out: null + }; + joCall.mapPendingByCallID.set( joIn.id, entry ); + entry.iv = setTimeout( function(): void { + if( entry.iv ) + clearTimeout( entry.iv ); + entry.iv = null; + joCall.mapPendingByCallID.delete( joIn.id ); + }, 200 * 1000 ); + joCall.wsConn.send( JSON.stringify( joIn ) ); + } else { + if( !validateURL( joCall.url ) ) { + if( fn ) { + await fn( joIn, null, + "JSON RPC CALLER cannot do query post to invalid URL: " + joCall.url ); + } + return; + } + const strBody = JSON.stringify( joIn ); + let errCall: string | null = null; let joOut: any | null = null; + if( joCall.joRpcOptions?.cert && typeof joCall.joRpcOptions.cert === "string" && + joCall.joRpcOptions.key && typeof joCall.joRpcOptions.key === "string" + ) { + const u = new URL( joCall.url ); + const options = { + hostname: u.hostname, + port: u.port, + path: "/", + method: "POST", + headers: { + "Content-Type": "application/json" + }, + ca: ( joCall.joRpcOptions?.ca && + typeof joCall.joRpcOptions.ca === "string" ) + ? joCall.joRpcOptions.ca + : null, + cert: ( joCall.joRpcOptions?.cert && + typeof joCall.joRpcOptions.cert === "string" ) + ? joCall.joRpcOptions.cert + : null, + key: ( joCall.joRpcOptions?.key && + typeof joCall.joRpcOptions.key === "string" ) + ? joCall.joRpcOptions.key + : null + }; + let accumulatedBody = ""; + const promiseComplete = new Promise( ( resolve, reject ) => { + try { + const req = https.request( options as https.RequestOptions, ( res: any ) => { + res.setEncoding( "utf8" ); + res.on( "data", function( body: any ): void { + accumulatedBody += body; + } ); + res.on( "end", function(): void { + if( res.statusCode !== 200 ) { + joOut = null; + errCall = "Response ends with bad status code: " + + res.statusCode.toString(); + reject( errCall ); + } + try { + joOut = JSON.parse( accumulatedBody ); + errCall = null; + resolve( joOut ); + } catch ( err ) { + joOut = null; + errCall = `Response body parse error: ${err as any}`; + reject( errCall ); + } + } ); + } ); + req.on( "error", function( err ): void { + log.error( "{url} REST error {err}", joCall.url, err as any ); + joOut = null; + errCall = `HTTP(S)/RPC call(event) error: ${err as any}`; + reject( errCall ); + } ); + req.write( strBody ); + req.end(); + } catch ( err ) { + log.error( "{url} REST error {err}", joCall.url, err as any ); + joOut = null; + errCall = `HTTP(S)/RPC call(processing) error: ${err as any}`; + reject( errCall ); + } + } ); + await promiseComplete.catch( function( err: Error | string ): void { + log.error( "{url} HTTP call error {err}", joCall.url, err ); + if( !errCall ) + errCall = `HTTP(S)/RPC call(catch) error: ${err as any}`; + } ); + } else { + try { + const requestOpts = { + method: "POST", + timeout: gSecondsConnectionTimeout * 1000, // in milliseconds + headers: { + "Content-Type": "application/json" + }, + content: strBody, + rejectUnauthorized: false, + // "requestCert": true, + agent: false, + httpsAgent: false, + ca: ( joCall.joRpcOptions?.ca && + typeof joCall.joRpcOptions.ca === "string" ) + ? joCall.joRpcOptions.ca + : null, + cert: ( joCall.joRpcOptions?.cert && + typeof joCall.joRpcOptions.cert === "string" ) + ? joCall.joRpcOptions.cert + : null, + key: ( joCall.joRpcOptions?.key && + typeof joCall.joRpcOptions.key === "string" ) + ? joCall.joRpcOptions.key + : null + }; + const response = + await urllib.request( joCall.url, requestOpts as urllib.RequestOptions ); + const body = response.data.toString( "utf8" ); + if( response?.statusCode !== 200 ) + log.warning( "REST call status code is {}", response.statusCode ); + + joOut = JSON.parse( body ); + errCall = null; + } catch ( err ) { + log.error( "{url} request error {err}", joCall.url, err as any ); + joOut = null; + errCall = `request error: ${err as any}`; + } + } + try { + if( fn ) + await fn( joIn, joOut, errCall ); + } catch ( err ) { + } + } +} + +export async function rpcCallCreate( + strURL: string, opts: TRPCCallOpts | null ): Promise { + if( !validateURL( strURL ) ) + throw new Error( `JSON RPC CALLER cannot create a call object invalid URL: ${strURL}` ); + if( !( strURL && typeof strURL === "string" && strURL.length > 0 ) ) { + throw new Error( "rpcCallCreate() was invoked with " + + `bad parameters: ${JSON.stringify( arguments )}` ); + } + const joCall: TRPCCall = { + url: strURL.toString(), + joRpcOptions: opts ?? null, + mapPendingByCallID: new Map < TCallID, TCallHandlerEntry >(), + wsConn: null, + isAutoReconnect: + !!( ( opts && "isAutoReconnect" in opts && opts.isAutoReconnect ) ), + isDisconnectMode: false, + reconnect: + async function( fnAfter: TFunctionConnectionResultHandler ): Promise { + await doConnect( joCall, opts, fnAfter ); + }, + reconnectIfNeeded: + async function( fnAfter: TFunctionConnectionResultHandler ): Promise { + await doConnectIfNeeded( joCall, opts, fnAfter ); + }, + call: + async function( joIn: any, fnAfter?: TFunctionCallResultHandler ): Promise { + const self = this; + const promiseComplete = new Promise < any >( function( resolve, reject ): void { + self.reconnectIfNeeded( + async function( joCall: TRPCCall, err: Error | string | null ): Promise { + if( err ) { + if( fnAfter ) + await fnAfter( joIn, null, err ); + reject( err ); + return; + } + await doCall( joCall, joIn, + async function( + joIn: any, joOut: any, err: Error | string | null + ): Promise { + if( fnAfter ) + await fnAfter( joIn, joOut, err ); + if( err ) + reject( err ); + else + resolve( joOut ); + } ).catch( function( err: Error | string ) { + log.error( + "{url} JSON RPC call(performer) error: {err}", strURL, err ); + } ); + } ).then( function(): void {} ).catch( function(): void {} ); ; + } ); + return await promiseComplete.catch( function( err: Error | string ) { + log.error( + "{url} JSON RPC call(awaiter) error: {err}", strURL, err ); + } ); + }, + disconnect: + async function( fnAfter?: TFunctionConnectionResultHandler ): Promise { + await doDisconnect( joCall, fnAfter ); + } + }; + await doConnect( joCall, opts ); + return joCall; +} + +export { rpcCallCreate as create }; + +export function generateRandomIntegerInRange( min: any, max: any ): number { + min = Math.ceil( min ); + max = Math.floor( max ); + return Math.floor( Math.random() * ( max - min + 1 ) ) + min; +} + +export function generateRandomRpcCallId(): number { + return generateRandomIntegerInRange( 1, Number.MAX_SAFE_INTEGER ); +} + +export function enrichTopLevelFieldsInJSON( jo: any ): any { + if( ( !( "jsonrpc" in jo ) ) || + ( typeof jo.jsonrpc !== "string" ) || + jo.jsonrpc.length === 0 + ) + jo.jsonrpc = "2.0"; + if( ( !( "id" in jo ) ) || ( typeof jo.id !== "number" ) || jo.id <= 0 ) + jo.id = generateRandomRpcCallId(); + return jo; +} + +export function isValidUrl( s: any ): boolean { + if( !s ) + return false; + try { + const u = new URL( s.toString() ); + if( u ) + return true; + } catch ( err ) { + } + return false; +} + +export function getValidUrl( s: any ): URL | null { + if( !s ) + return null; + try { + return new URL( s.toString() ); + } catch ( err ) { + } + return null; +} + +export function getDefaultPort( strProtocol: any ): number { + if( !strProtocol ) + return 80; + switch ( strProtocol.toString().toLowerCase() ) { + case "http:": + case "ws:": + return 80; + case "https:": + case "wss:": + return 443; + } + return 80; +} + +export function getValidHostAndPort( s: any ): any { + const u = getValidUrl( s ); + if( !u ) + return null; + const jo = { + strHost: u.hostname, + nPort: u.port ? parseInt( u.port, 10 ) : getDefaultPort( u.protocol ) + }; + return jo; +} + +const gStrTcpConnectionHeader: string = "TCP connection checker: "; + +export async function checkTcpPromise( + strHost: string, nPort: number, nTimeoutMilliseconds: number, isLog?: boolean +): Promise { + return await new Promise( ( resolve, reject ) => { + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}Will establish ` + + `TCP connection to ${strHost}:${nPort}...` ); + } + const conn = net.createConnection( { host: strHost, port: nPort }, () => { + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}Done, ` + + `TCP connection to ${strHost}:${nPort} established` ); + } + conn.end(); + resolve( true ); + } ); + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}Did created NET object ` + + `for TCP connection to ${strHost}:${nPort}...` ); + } + if( nTimeoutMilliseconds ) + nTimeoutMilliseconds = parseInt( nTimeoutMilliseconds.toString(), 10 ); + if( nTimeoutMilliseconds > 0 ) { + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}Will use ` + + `TCP connection to ${strHost}:${nPort} ` + + `timeout ${nTimeoutMilliseconds} milliseconds...` ); + } + conn.setTimeout( nTimeoutMilliseconds ); + } else { + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}Will use ` + + `default TCP connection to ${strHost}:${nPort} timeout...` ); + } + } + conn.on( "timeout", + function( err: Error | string | null ): void { + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}TCP connection ` + + `to ${strHost}:${nPort} timed out` ); + } + conn.destroy(); + reject( err ); + } ); + conn.on( "error", function( err: Error | string ): void { + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}TCP connection ` + + `to ${strHost}:${nPort} failed` ); + } + reject( err ); + } ); + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}TCP connection ` + + `to ${strHost}:${nPort} check started...` ); + } + } ); +} + +export async function checkTcp( + strHost: string, nPort: number, nTimeoutMilliseconds: number, isLog?: boolean +): Promise { + let isOnline = false; + try { + const promiseCompleteTcpCheck = checkTcpPromise( + strHost, nPort, nTimeoutMilliseconds, isLog ) + .then( function(): void { isOnline = true; } ) + .catch( function(): void { isOnline = false; } ); + + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}Waiting for ` + + `TCP connection to ${strHost}:${nPort} check done...` ); + } + await promiseCompleteTcpCheck.catch( function(): void { isOnline = false; } ); + if( isLog ) { + console.log( + `${gStrTcpConnectionHeader}TCP connection ` + + `to ${strHost}:${nPort} check finished` ); + } + } catch ( err ) { + isOnline = false; + console.log( + `${gStrTcpConnectionHeader}TCP connection ` + + `to ${strHost}:${nPort} check failed with error: ${err as any}` ); + } + return isOnline; +} + +export async function checkUrl( + u: URL | string, nTimeoutMilliseconds: number, isLog?: boolean ): Promise { + if( !u ) + return false; + const jo = getValidHostAndPort( u ); + if( isLog ) { + console.log( `${gStrTcpConnectionHeader}Extracted from URL ${u.toString()} data ` + + `fields are: ${JSON.stringify( jo )}` ); + } + if( !( jo?.strHost && "nPort" in jo ) ) { + console.log( `${gStrTcpConnectionHeader}Extracted from URL ${u.toString()} data ` + + "fields are bad, returning \"false\" as result of TCP connection check" ); + return false; + } + return await checkTcp( jo.strHost, jo.nPort, nTimeoutMilliseconds, isLog ); +} diff --git a/src/socket.ts b/src/socket.ts new file mode 100644 index 00000000..db5a731f --- /dev/null +++ b/src/socket.ts @@ -0,0 +1,3720 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE COOL SOCKET + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file socket.ts + * @copyright SKALE Labs 2019-Present + */ + +import { UniversalDispatcherEvent, EventDispatcher } from "./eventDispatcher.js"; +import { settings } from "./socketSettings.js"; +import * as utils from "./socketUtils.js"; +import * as cc from "./cc.js"; + +export let httpsModule: any = null; // server side only +export let wsModule: any = null; // server side only +export let webRtcModule: any = null; // server side only + +export function setHttpsModule( mod: any ): void { + httpsModule = mod || null; +} +export function setWsModule( mod: any ): void { + wsModule = mod || null; +} +export function setWebRtcModule( mod: any ): void { + webRtcModule = mod || null; +} + +export const gMapLocalServers: any = { }; // used both for local and in-worker servers + +export const socketSentDataMarshall = function( data?: any ): any { + const s = data + ? ( ( typeof data === "string" ) + ? data + : ( ( typeof data === "object" ) ? JSON.stringify( data ) : data.toString() ) + ) + : ""; + return s; +}; +export const socketReceivedDataReverseMarshall = function( data?: any ): any { + try { + const jo: any = data + ? ( ( typeof data === "object" ) + ? data + : ( ( typeof data === "string" ) ? JSON.parse( data ) : data ) + ) + : { }; + return jo; + } catch ( err ) { + return { + error: true, + message: "data un-marshal error", + data + }; + } +}; + +export const updateSocketDataStatsForMessage = function( joMessage: any, joStats: any ): void { + let strMethod = "_N/A_"; + if( joMessage?.method ) + strMethod = joMessage.method.toString(); + if( strMethod in joStats ) + joStats[strMethod]++; + else + joStats[strMethod] = 1; +}; +export const generateSocketDataStatsJSON = function( jo: any ): any { + const joStats: any = {}; + if( "arrPackedMessages" in jo && + jo.arrPackedMessages && + typeof jo.arrPackedMessages === "object" + ) { + for( const joMessage of jo.arrPackedMessages ) + updateSocketDataStatsForMessage( joMessage, joStats ); + } else + updateSocketDataStatsForMessage( jo, joStats ); + return joStats; +}; + +export class BasicServerAcceptor extends EventDispatcher { + socketType: string; + socketSubtype: string; + isListening: boolean; + strEndPoint: string | null; + nextClientNumber: number; + mapClients: any; + url?: string; + constructor () { + super(); + this.socketType = "BasicAcceptor"; + this.socketSubtype = "acceptor"; + this.isListening = false; + this.strEndPoint = null; + this.nextClientNumber = 1; + this.mapClients = { }; + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.nextClientNumber = 1; + this.isListening = false; + this.disposeNotifyClients(); + super.dispose(); + } + + disposeNotifyClients(): void { + for( const [ /* keyWalk */, entryWalk ] of Object.entries( this.mapClients ) ) { + const entry: any = entryWalk; + if( ( "serverPipe" in entry ) && ( "clientPipe" in entry ) ) { + const pair: any = entry; + pair.serverPipe.handleServerDisposed(); + pair.clientPipe.handleServerDisposed(); + pair.serverPipe = null; + pair.clientPipe = null; + } else { + const pipe = entry; + pipe.handleServerDisposed(); + } + } + this.mapClients = { }; + } + + unregisterClientByKey( key: any ): void { + if( key in this.mapClients ) { + const entry = this.mapClients[key.toString()]; + if( entry ) { + if( ( "serverPipe" in entry ) && ( "clientPipe" in entry ) ) { + const pair = entry; + pair.serverPipe = null; + pair.clientPipe = null; + } + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.mapClients[key.toString()]; + } + } + } + + flush(): void { + if( this.isDisposing || this.isDisposed ) + return; + for( const [ /* keyWalk */, entryWalk ] of Object.entries( this.mapClients ) ) { + const entry: any = entryWalk; + if( ( "serverPipe" in entry ) && ( "clientPipe" in entry ) ) { + const pair: any = entry; + pair.serverPipe.flush(); + } else { + const pipe = entry; + pipe.flush(); + } + } + } + + newDirectConnection(): DirectPipe | null { + if( this.isDisposing || this.isDisposed ) + return null; + if( !this.isListening ) + return null; + const clientPipe: DirectPipe = new DirectPipe( null, false ); + const serverPipe: DirectPipe = new DirectPipe( clientPipe, false ); + serverPipe.acceptor = this; + this.mapClients[serverPipe.clientPort.toString()] = serverPipe; + const self = this; + const iv = setTimeout( function(): void { + clearTimeout( iv ); + serverPipe.dispatchEvent( + new UniversalDispatcherEvent( "open", { socket: serverPipe } ) ); + self.dispatchEvent( + new UniversalDispatcherEvent( + "connection", + { socket: serverPipe, remoteAddress: self.url ? self.url.toString() : "" } ) ); + clientPipe.dispatchEvent( + new UniversalDispatcherEvent( "open", { socket: clientPipe } ) ); + }, 0 ); + return clientPipe; + } +}; + +export class BasicSocketPipe extends EventDispatcher { + socketType: string; + socketSubtype: string; + url: string; + isConnected: boolean; + arrAccumulatedMessages: any[]; + maxAccumulatedMessagesCount: number; + relayClientSocket: any; + mapImpersonatedEntries: any; // for external in-app usage only + acceptor: any; + clientPort: any; + logicalInitComplete: any; // for external use + errorLogicalInit: any; // for external use + constructor () { + super(); + this.socketType = "N/A"; + this.socketSubtype = "N/A"; + this.url = "N/A"; + this.isConnected = true; + this.arrAccumulatedMessages = []; + this.maxAccumulatedMessagesCount = + cc.toInteger( settings.net.pipe.maxAccumulatedMessagesCount ); + this.relayClientSocket = null; // for relay only + this.mapImpersonatedEntries = { }; // for external in-app usage only + } + + dispose(): void { + if( this.relayClientSocket ) { + this.relayClientSocket.dispose(); + this.relayClientSocket = null; + } + this.disposeImpersonatedEntries(); // for external in-app usage only + this.disconnect(); + this.arrAccumulatedMessages = []; + super.dispose(); + } + + disposeImpersonatedEntries(): void { // for external in-app usage only + for( const [ /* keyWalk */, entryWalk ] of Object.entries( this.mapImpersonatedEntries ) ) { + const entry: any = entryWalk; + try { + if( entry && "dispose" in entry && typeof entry.dispose === "function" ) + entry.dispose(); + } catch ( err ) { + } + } + this.mapImpersonatedEntries = { }; // for app usage + } + + implSend( data: any ): void { + throw new Error( + "BasicSocketPipe.implSend() must be overridden but calling it was attempted" ); + } + + isAutoFlush(): boolean { + if( this.maxAccumulatedMessagesCount <= 1 ) + return true; + const cnt = this.arrAccumulatedMessages.length; + if( cnt == 0 || cnt < this.maxAccumulatedMessagesCount ) + return false; + return true; + } + + socketDescription(): string { + return this.url.toString(); + } + + socketLoggingTextPrefix( strLogEventName: string ): string { + return strLogEventName + " " + this.socketDescription() + " -"; + } + + send( data: any, isFlush?: boolean ): void { + if( this.isDisposed || ( !this.isConnected ) ) + return; + if( this.isAutoFlush() ) { + if( settings.logging.net.socket.send || settings.logging.net.socket.flush ) + console.log( this.socketLoggingTextPrefix( "send+flush" ), data ); + this.implSend( data ); + return; + } + isFlush = ( isFlush == undefined || isFlush == null ) ? true : ( !!( isFlush ) ); + const jo: any = socketReceivedDataReverseMarshall( data ); + if( settings.logging.net.socket.accumulate ) + console.log( this.socketLoggingTextPrefix( "accumulate" ), data ); + this.arrAccumulatedMessages.push( jo ); + if( isFlush ) + this.flush(); + } + + flush(): void { + if( this.isDisposed || ( !this.isConnected ) ) + return; + const cnt = this.arrAccumulatedMessages.length; + if( cnt == 0 ) + return; + if( settings.logging.net.socket.flushCount ) + console.log( this.socketLoggingTextPrefix( "flush-count(" + cnt + ")" ) ); + let joSend: any = null; + if( cnt == 1 ) { + joSend = this.arrAccumulatedMessages[0]; + if( settings.logging.net.socket.flushOne || settings.logging.net.socket.flush ) + console.log( this.socketLoggingTextPrefix( "flush-one" ), joSend ); + } else { + joSend = { arrPackedMessages: this.arrAccumulatedMessages }; + if( settings.logging.net.socket.flushBlock || settings.logging.net.socket.flush ) + console.log( this.socketLoggingTextPrefix( "flush-block(" + cnt + ")" ), joSend ); + } + if( settings.logging.net.socket.flushMethodStats ) { + console.log( + this.socketLoggingTextPrefix( "flush-method-stats(" + cnt + ")" ), + generateSocketDataStatsJSON( joSend ) + ); + } + this.implSend( joSend ); + this.arrAccumulatedMessages = []; + if( this.relayClientSocket ) + this.relayClientSocket.flush(); + } + + implReceive( data: any ): void { + const jo: any = socketReceivedDataReverseMarshall( data ); + this.dispatchEvent( + new UniversalDispatcherEvent( "message", { socket: this, message: jo } ) ); + } + + receive( data: any ): void { + if( settings.logging.net.socket.receiveBlock ) + console.log( this.socketLoggingTextPrefix( "receive-block" ), data ); + const jo: any = socketReceivedDataReverseMarshall( data ); + if( "arrPackedMessages" in jo && + jo.arrPackedMessages && + typeof jo.arrPackedMessages === "object" + ) { + const cnt = jo.arrPackedMessages.length; + if( settings.logging.net.socket.receiveCount ) + console.log( this.socketLoggingTextPrefix( "receive-count(" + cnt + ")" ) ); + if( settings.logging.net.socket.receiveMethodStats ) { + console.log( + this.socketLoggingTextPrefix( "receive-method-stats(" + cnt + ")" ), + generateSocketDataStatsJSON( jo ) + ); + } + for( let i = 0; i < cnt; ++i ) { + const joMessage = jo.arrPackedMessages[i]; + if( settings.logging.net.socket.receive ) + console.log( this.socketLoggingTextPrefix( "receive" ), joMessage ); + this.implReceive( joMessage ); + } + return; + } + if( settings.logging.net.socket.receiveCount ) + console.log( this.socketLoggingTextPrefix( "receive-count(" + 1 + ")" ) ); + if( settings.logging.net.socket.receiveMethodStats ) { + console.log( + this.socketLoggingTextPrefix( + "receive-method-stats(" + 1 + ")" ), generateSocketDataStatsJSON( jo ) ); + } + if( settings.logging.net.socket.receive ) + console.log( this.socketLoggingTextPrefix( "receive" ), jo ); + this.implReceive( jo ); + } + + disconnect(): void { + this.isConnected = false; + } + + reconnect(): void { + } + + checkItself(): void { + } +}; + +export class NullSocketPipe extends BasicSocketPipe { + constructor() { + super(); + this.socketType = "NULL"; + this.socketSubtype = "pipe"; + this.url = "NullUrl"; + this.isConnected = true; + } + + dispose(): void { + this.isConnected = false; + super.dispose(); + } + + implSend( data: any ): void { + } + + implReceive( data: any ): void { + } + + send( data: any ): void { + } + + receive( data: any ): void { + } + + flush(): void { + } +}; + +export const isRunningInWorker = function(): boolean { + if( self.document === undefined ) + return true; + return false; +}; + +// in-worker clients in connecting state +export const gMapAwaitingInWorkerClients: Record < string, any > = { }; +// in-worker clients in connecting state +export const gMapConnectedInWorkerClients: Record < string, any > = { }; + +export const outOfWorkerAPIs: any = { + onMessage: function( worker: any, data: any ): boolean { + const jo: any = socketReceivedDataReverseMarshall( data ); + if( !( "workerMessageType" in jo ) || + typeof jo.workerMessageType !== "string" || + jo.workerMessageType.length == 0 ) + return false; // not a socket message + if( !( "workerEndPoint" in jo ) || + typeof jo.workerEndPoint !== "string" || + jo.workerEndPoint.length == 0 ) + return false; // TO-DO: send error answer and return true + if( !( "workerUUID" in jo ) || typeof jo.workerUUID !== "string" || + jo.workerUUID.length == 0 ) + return false; // TO-DO: send error answer and return true + switch ( jo.workerMessageType ) { + case "inWorkerConnect": { + if( !( jo.workerUUID in gMapAwaitingInWorkerClients ) ) + return false; + const pipe: any = gMapAwaitingInWorkerClients[jo.workerUUID.toString()]; + pipe.performSuccessfulConnection(); + } return true; + case "inWorkerDisconnect": { + if( !( jo.workerUUID in gMapConnectedInWorkerClients ) ) + return false; + const pipe: any = gMapConnectedInWorkerClients[jo.workerUUID]; + pipe.performDisconnect(); + } return true; + case "inWorkerMessage": { + if( !( jo.workerUUID in gMapConnectedInWorkerClients ) ) + return false; + const pipe: any = gMapConnectedInWorkerClients[jo.workerUUID]; + pipe.receive( jo.data ); + } return true; + default: + return false; // TO-DO: send error answer and return true + } // switch( jo.workerMessageType ) + }, + onSendMessage: function( + worker: any, type: any, endpoint: any, workerUUID: any, data: any ): void { + const jo: any = socketReceivedDataReverseMarshall( data ); + const joSend: any = { + workerMessageType: + ( type && typeof type === "string" && type.length > 0 ) + ? type + : "inWorkerMessage", + workerEndPoint: endpoint, + workerUUID, + data: jo + }; + // worker.postMessage( socketReceivedDataReverseMarshall( joSend ) ); + worker.postMessage( socketSentDataMarshall( joSend ) ); + } +}; +export const inWorkerAPIs: any = { + onMessage: function( data: any ): boolean { + const jo: any = socketReceivedDataReverseMarshall( data ); + if( !( "workerMessageType" in jo ) || + typeof jo.workerMessageType !== "string" || + jo.workerMessageType.length == 0 ) + return false; // not a socket message + if( !( "workerEndPoint" in jo ) || + typeof jo.workerEndPoint !== "string" || + jo.workerEndPoint.length == 0 ) + return false; // TO-DO: send error answer and return true + if( !( "workerUUID" in jo ) || + typeof jo.workerUUID !== "string" || + jo.workerUUID.length == 0 ) + return false; // TO-DO: send error answer and return true + if( !( jo.workerEndPoint in gMapLocalServers ) ) + return false; // TO-DO: send error answer and return true + const acceptor = gMapLocalServers[jo.workerEndPoint]; + switch ( jo.workerMessageType ) { + case "inWorkerConnect": + return acceptor.performAccept( jo ); + case "inWorkerDisconnect": + return acceptor.performDisconnect( jo ); + case "inWorkerMessage": + return acceptor.receiveForClientPort( jo.workerUUID, jo.data ); + default: + return false; // TO-DO: send error answer and return true + } // switch( jo.workerMessageType ) + }, + onSendMessage: function( type: any, endpoint: any, workerUUID: any, data: any ): void { + const jo: any = socketReceivedDataReverseMarshall( data ); + const joSend: any = { + workerMessageType: + ( type && typeof type === "string" && type.length > 0 ) + ? type + : "inWorkerMessage", + workerEndPoint: endpoint, + workerUUID, + data: jo + }; + // postMessage( socketReceivedDataReverseMarshall( joSend ) ); + postMessage( socketSentDataMarshall( joSend ) ); + } +}; + +export class InWorkerServerPipe extends BasicSocketPipe { + fnSend: any; + constructor ( acceptor: any, clientPort: string, fnSend: any ) { + super(); + this.socketType = "InWorker"; + this.socketSubtype = "server"; + this.isConnected = true; + this.acceptor = acceptor; + this.clientPort = clientPort.toString(); + this.fnSend = fnSend || inWorkerAPIs.onSendMessage; + this.url = "in_worker_server_pipe://" + acceptor.strEndPoint + ":" + clientPort; + this.acceptor.mapClients[this.clientPort.toString()] = this; + this.fnSend( "inWorkerConnect", this.acceptor.strEndPoint, this.clientPort, {} ); + const self = this; + const iv = setTimeout( function(): void { + clearTimeout( iv ); + self.dispatchEvent( new UniversalDispatcherEvent( "open", { socket: self } ) ); + self.acceptor.dispatchEvent( + new UniversalDispatcherEvent( + "connection", { socket: self, remoteAddress: self.url.toString() } ) ); + }, 0 ); + } + + dispose(): void { + this.performDisconnect(); + super.dispose(); + } + + handleServerDisposed(): void { + this.performDisconnect(); + this.isConnected = false; + this.dispatchEvent( new UniversalDispatcherEvent( "close", { socket: this } ) ); + this.acceptor = null; + this.fnSend = null; + this.url = ""; + this.dispose(); + } + + performDisconnect(): void { + if( !this.isConnected ) + return; + this.fnSend( "inWorkerDisconnect", this.acceptor.strEndPoint, this.clientPort, {} ); + this.isConnected = false; + if( this.acceptor ) + this.acceptor.unregisterClientByKey( this.clientPort ); + this.dispatchEvent( new UniversalDispatcherEvent( "close", { socket: this } ) ); + this.acceptor = null; + this.fnSend = null; + this.url = ""; + } + + implSend( data: any ): void { + if( ( !this.isConnected ) || ( !this.fnSend ) || typeof this.fnSend !== "function" ) { + const s = "Cannot send messages to disconnected in-worker server pipe"; + this.dispatchEvent( + new UniversalDispatcherEvent( "error", { socket: this, message: s.toString() } ) ); + throw new Error( s ); + } + const jo: any = socketReceivedDataReverseMarshall( data ); + this.fnSend( "inWorkerMessage", this.acceptor.strEndPoint, this.clientPort, jo ); + } + + disconnect(): void { + this.performDisconnect(); + super.disconnect(); + } +}; + +export class InWorkerSocketServerAcceptor extends BasicServerAcceptor { + fnSend: any; + constructor ( strEndPoint: string, fnSend: any ) { + super(); + this.socketType = "InWorker"; + this.strEndPoint = + ( strEndPoint && typeof strEndPoint === "string" && strEndPoint.length > 0 ) + ? strEndPoint + : "default_local_endpoint"; + if( this.strEndPoint in gMapLocalServers ) { + const s = + "Cannot start in-worker socket server on already listening \"" + + this.strEndPoint + "\" endpoint"; + this.dispatchEvent( + new UniversalDispatcherEvent( "error", { socket: this, message: s.toString() } ) ); + throw new Error( s ); + } + gMapLocalServers[this.strEndPoint] = this; + this.fnSend = fnSend || inWorkerAPIs.onSendMessage; + this.isListening = true; + const self = this; + const iv = setTimeout( function(): void { + clearTimeout( iv ); + self.dispatchEvent( new UniversalDispatcherEvent( "open", { socket: self } ) ); + }, 0 ); + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.disposeNotifyClients(); + if( this.strEndPoint && + typeof this.strEndPoint === "string" && + this.strEndPoint.length > 0 + ) { + if( this.strEndPoint in gMapLocalServers ) + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete gMapLocalServers[this.strEndPoint]; + } + super.dispose(); + } + + performAccept( jo: any ): boolean { + if( jo.workerUUID in this.mapClients ) + return false; // TO-DO: send error answer and return true + const p: InWorkerServerPipe = + new InWorkerServerPipe( this, jo.workerUUID.toString(), this.fnSend ); + p.checkItself(); + return true; + } + + performDisconnect( jo: any ): boolean { + if( !( jo.workerUUID in this.mapClients ) ) + return false; // TO-DO: send error answer and return true + const pipe = this.mapClients[jo.workerUUID.toString()]; + pipe.performDisconnect(); + return true; + } + + receiveForClientPort( clientPort: any, jo: any ): boolean { + if( !( clientPort in this.mapClients ) ) + return false; // TO-DO: send error answer and return true + const pipe = this.mapClients[clientPort.toString()]; + pipe.receive( jo ); + return true; + } +}; + +export class OutOfWorkerSocketClientPipe extends BasicSocketPipe { + worker: any; + fnSend: any; + strEndPoint: string; + constructor ( strEndPoint: string, worker: any, fnSend?: any ) { + super(); + this.socketType = "InWorker"; + this.socketSubtype = "client"; + this.isConnected = false; + this.worker = worker; + this.clientPort = utils.UUIDv4(); + this.strEndPoint = + ( strEndPoint && + typeof strEndPoint === "string" && + strEndPoint.length > 0 + ) + ? strEndPoint + : "default_in_worker_endpoint"; + this.url = "out_of_worker_client_pipe://" + this.strEndPoint + ":" + this.clientPort; + this.fnSend = fnSend || outOfWorkerAPIs.onSendMessage; + this.fnSend( this.worker, "inWorkerConnect", this.strEndPoint, this.clientPort, {} ); + gMapAwaitingInWorkerClients[this.clientPort.toString()] = this; + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.performDisconnect(); + if( this.clientPort in gMapAwaitingInWorkerClients ) + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete gMapAwaitingInWorkerClients[this.clientPort]; + super.dispose(); + } + + performDisconnect(): void { + if( !this.isConnected ) + return; + this.isConnected = false; + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete gMapConnectedInWorkerClients[this.clientPort.toString()]; + this.fnSend( this.worker, "inWorkerDisconnect", this.strEndPoint, this.clientPort, {} ); + this.dispatchEvent( new UniversalDispatcherEvent( "close", { socket: this } ) ); + this.worker = null; + this.clientPort = ""; + this.strEndPoint = ""; + this.url = ""; + } + + performSuccessfulConnection(): void { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete gMapAwaitingInWorkerClients[this.clientPort]; + gMapConnectedInWorkerClients[this.clientPort.toString()] = this; + this.isConnected = true; + this.dispatchEvent( new UniversalDispatcherEvent( "open", { socket: this } ) ); + } + + implSend( data: any ): void { + if( ( !this.isConnected ) || + ( !this.worker ) || + ( !this.fnSend ) || + typeof this.fnSend !== "function" + ) { + const s = "Cannot send messages to disconnected in-worker client pipe"; + this.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { socket: this, message: s.toString() } ) + ); + throw new Error( s ); + } + const jo: any = socketReceivedDataReverseMarshall( data ); + this.fnSend( this.worker, "inWorkerMessage", this.strEndPoint, this.clientPort, jo ); + } + + disconnect(): void { + this.performDisconnect(); + super.disconnect(); + } +}; + +export class OutOfWorkerRelay extends EventDispatcher { + strRelayName?: string; + isAutoFlushIncoming?: boolean; + isAutoFlushOutgoing?: boolean; + acceptor: any; + fnCreateClient: any; + onConnection_: any; + // eslint-disable-next-line max-lines-per-function + constructor ( + strRelayName: string, acceptor: any, fnCreateClient: any, + isAutoFlushIncoming: boolean, isAutoFlushOutgoing: boolean ) { + super(); + const self = this; + self.strRelayName = strRelayName ? strRelayName.toString() : "unnamed"; + self.isAutoFlushIncoming = isAutoFlushIncoming ? true : ( !!isAutoFlushIncoming ); + self.isAutoFlushOutgoing = isAutoFlushOutgoing ? true : ( !!isAutoFlushOutgoing ); + if( !acceptor ) { + throw new Error( `OutOfWorkerRelay ${self.strRelayName} needs acceptor ` + + "for normal functionality" ); + } + if( typeof fnCreateClient !== "function" ) { + throw new Error( `OutOfWorkerRelay ${self.strRelayName} needs callback ` + + "to create connections to target server" ); + } + self.acceptor = acceptor; + self.fnCreateClient = fnCreateClient; + // eslint-disable-next-line max-lines-per-function + self.onConnection_ = function( eventData: any ): void { + const pipeIncoming: any = eventData.socket; + let pipeOutgoing: any = null; + if( ( !( "remoteAddress" in eventData ) ) || + eventData.remoteAddress == null || eventData.remoteAddress == undefined ) + pipeIncoming.strSavedRemoteAddress = pipeIncoming.constructor.name; + else + pipeIncoming.strSavedRemoteAddress = eventData.remoteAddress.toString(); + if( settings.logging.net.relay.connect ) { + console.log( "Relay \"" + self.strRelayName + + "\" got new external-client connection \"" + + pipeIncoming.strSavedRemoteAddress + "\"" ); + } + self.dispatchEvent( new UniversalDispatcherEvent( + "connection", { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString() + } ) ); + // 1) configure incoming pipe + let _offAllPipeEventListeners: any = null; + let _onExternalPipeClose: any = function(): void { + if( settings.logging.net.relay.disconnect ) { + console.warn( "Relay \"" + self.strRelayName + + "\" external-client socket closed \"" + + pipeIncoming.strSavedRemoteAddress + "\"" ); + } + self.dispatchEvent( new UniversalDispatcherEvent( + "close", { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: true + } ) ); + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + }; + let _onRelayPipeClose: any = function(): void { + if( settings.logging.net.relay.disconnect ) { + console.warn( "Relay \"" + self.strRelayName + + "\" relay-client socket closed \"" + + pipeIncoming.strSavedRemoteAddress + "\"" ); + } + self.dispatchEvent( new UniversalDispatcherEvent( + "close", { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: false + } ) ); + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + }; + let _onExternalPipeError: any = function( eventData: any ): void { + if( settings.logging.net.relay.error ) { + console.warn( "Relay client \"" + self.strRelayName + + "\" external-client socket error \"" + + pipeIncoming.strSavedRemoteAddress + "\"" ); + } + self.dispatchEvent( new UniversalDispatcherEvent( + "error", { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: true + } ) ); + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + }; + let _onRelayPipeError: any = function( eventData: any ): void { + if( settings.logging.net.relay.error ) { + console.warn( "Relay client \"" + self.strRelayName + + "\" relay-client socket error \"" + + pipeIncoming.strSavedRemoteAddress + "\"" ); + } + self.dispatchEvent( new UniversalDispatcherEvent( + "error", { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: false + } ) ); + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + }; + let _onExternalPipeMessage: any = function( eventData: any ): void { + if( settings.logging.net.relay.rawMessage ) { + console.log( "Relay \"" + self.strRelayName + "\" external-client socket \"" + + eventData.strSavedRemoteAddress + "\" raw message", eventData ); + } + const joMessage = eventData.message; + if( settings.logging.net.relay.message ) { + console.log( "Relay \"" + self.strRelayName + "\" external-client socket \"" + + pipeIncoming.strSavedRemoteAddress + "\" message ", joMessage ); + } + if( !pipeOutgoing ) { + throw new Error( `Relay ${self.strRelayName} is not completely initialized ` + + "and cannot transfer messages" ); + } + self.dispatchEvent( new UniversalDispatcherEvent( + "message", { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: true, + message: joMessage + } ) ); + pipeOutgoing.send( joMessage ); + if( self.isAutoFlushIncoming ) + pipeOutgoing.flush(); + }; + let _onRelayPipeMessage: any = function( eventData: any ): void { + if( settings.logging.net.relay.rawMessage ) { + console.log( "Relay \"" + self.strRelayName + "\" relay-client socket \"" + + eventData.strSavedRemoteAddress + "\" raw message", eventData ); + } + const joMessage = eventData.message; + if( settings.logging.net.relay.message ) { + console.log( "Relay \"" + self.strRelayName + "\" relay-client socket \"" + + pipeIncoming.strSavedRemoteAddress + "\" message ", joMessage ); + } + if( !pipeOutgoing ) { + throw new Error( `Relay ${self.strRelayName} is not completely initialized ` + + "and cannot transfer messages" ); + } + self.dispatchEvent( new UniversalDispatcherEvent( + "message", { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: false, + message: joMessage + } ) ); + pipeOutgoing.send( joMessage ); + if( self.isAutoFlushOutgoing ) + pipeOutgoing.flush(); + }; + _offAllPipeEventListeners = function(): void { + if( _onExternalPipeClose ) { + pipeIncoming.off( "close", _onExternalPipeClose ); + _onExternalPipeClose = null; + } + if( _onExternalPipeError ) { + pipeIncoming.off( "error", _onExternalPipeError ); + _onExternalPipeError = null; + } + if( _onExternalPipeMessage ) { + pipeIncoming.off( "message", _onExternalPipeMessage ); + _onExternalPipeMessage = null; + } + if( pipeOutgoing.relayClientSocket ) { + if( _onRelayPipeClose ) { + pipeOutgoing.off( "close", _onRelayPipeClose ); + _onRelayPipeClose = null; + } + if( _onRelayPipeError ) { + pipeOutgoing.off( "error", _onRelayPipeError ); + _onRelayPipeError = null; + } + if( _onRelayPipeMessage ) { + pipeOutgoing.off( "message", _onRelayPipeMessage ); + _onRelayPipeMessage = null; + } + pipeOutgoing.disconnect(); + pipeOutgoing.dispose(); + } + pipeIncoming.disconnect(); + pipeIncoming.dispose(); + }; + pipeIncoming.on( "close", _onExternalPipeClose ); + pipeIncoming.on( "error", _onExternalPipeError ); + pipeIncoming.on( "message", _onExternalPipeMessage ); + // 2) configure outgoing relay client pipe + pipeOutgoing = pipeIncoming.relayClientSocket = self.fnCreateClient(); + if( !pipeOutgoing ) { + pipeIncoming.dispose(); + throw new Error( `Relay ${self.strRelayName} failed to initialize ` + + "relay-client socket to target server" ); + } + pipeOutgoing.on( "close", _onRelayPipeClose ); + pipeOutgoing.on( "error", _onRelayPipeError ); + pipeOutgoing.on( "message", _onRelayPipeMessage ); + }; + self.acceptor.on( "connection", self.onConnection_ ); + } + + dispose(): void { + this.isDisposing = true; + if( this.acceptor ) + this.acceptor.off( "connection", this.onConnection_ ); + this.onConnection_ = null; + super.dispose(); + } + + flush(): void { + if( this.acceptor ) + this.acceptor.flush(); + } +}; + +export class OneToOneRelay extends EventDispatcher { + strRelayName?: string; + isAutoFlushIncoming?: boolean; + isAutoFlushOutgoing?: boolean; + pipeIncoming: any; + pipeOutgoing: any; + // eslint-disable-next-line max-lines-per-function + constructor ( + strRelayName: string, pipeIncoming: any, pipeOutgoing: any, + isAutoFlushIncoming: boolean, isAutoFlushOutgoing: boolean + ) { + super(); + const self = this; + self.strRelayName = strRelayName ? strRelayName.toString() : "unnamed"; + self.isAutoFlushIncoming = isAutoFlushIncoming ? true : ( !!isAutoFlushIncoming ); + self.isAutoFlushOutgoing = isAutoFlushOutgoing ? true : ( !!isAutoFlushIncoming ); + self.pipeIncoming = pipeIncoming; + self.pipeOutgoing = pipeOutgoing; + if( ( !( "strSavedRemoteAddress" in pipeIncoming ) ) || + pipeIncoming.strSavedRemoteAddress == null || + pipeIncoming.strSavedRemoteAddress == undefined ) + pipeIncoming.strSavedRemoteAddress = pipeIncoming.constructor.name.toString(); + if( ( !( "strSavedRemoteAddress" in pipeOutgoing ) ) || + pipeOutgoing.strSavedRemoteAddress == null || + pipeOutgoing.strSavedRemoteAddress == undefined ) + pipeOutgoing.strSavedRemoteAddress = pipeOutgoing.constructor.name.toString(); + + // 1) configure incoming pipe + let _offAllPipeEventListeners: any = null; + let _onIncomingPipeClose: any = function(): void { + if( settings.logging.net.relay.disconnect ) { + console.warn( + "Relay \"" + self.strRelayName + "\" incoming-client socket closed \"" + + pipeIncoming.strSavedRemoteAddress + "\"" + ); + } + self.dispatchEvent( new UniversalDispatcherEvent( + "close", + { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: true + } ) + ); + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + }; + let _onOutgoingPipeClose: any = function(): void { + if( settings.logging.net.relay.disconnect ) { + console.warn( + "Relay \"" + self.strRelayName + "\" outgoing-client socket closed \"" + + pipeIncoming.strSavedRemoteAddress + "\"" + ); + } + self.dispatchEvent( new UniversalDispatcherEvent( + "close", + { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: false + } ) + ); + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + }; + let _onIncomingPipeError: any = function( eventData: any ): void { + if( settings.logging.net.relay.error ) { + console.warn( + "Relay client \"" + self.strRelayName + + "\" incoming-client socket error \"" + + pipeIncoming.strSavedRemoteAddress + "\"" + ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: true + } ) + ); + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + }; + let _onOutgoingPipeError: any = function( eventData: any ): void { + if( settings.logging.net.relay.error ) { + console.warn( + "Relay client \"" + self.strRelayName + + "\" outgoing-client socket error \"" + + pipeIncoming.strSavedRemoteAddress + "\"" + ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: false + } ) + ); + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + }; + let _onIncomingPipeMessage: any = function( eventData: any ): void { + if( settings.logging.net.relay.rawMessage ) { + console.log( + "Relay \"" + self.strRelayName + "\" incoming-client socket \"" + + eventData.strSavedRemoteAddress + "\" raw message", eventData + ); + } + const joMessage = eventData.message; + if( settings.logging.net.relay.message ) { + console.log( + "Relay \"" + self.strRelayName + "\" incoming-client socket \"" + + pipeIncoming.strSavedRemoteAddress + "\" message ", joMessage + ); + } + if( !pipeOutgoing ) { + throw new Error( `Relay ${self.strRelayName} is not completely initialized ` + + "and cannot transfer messages" ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "message", + { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: true, + message: joMessage + } ) + ); + pipeOutgoing.send( joMessage ); + if( self.isAutoFlushIncoming ) + pipeOutgoing.flush(); + }; + let _onOutgoingPipeMessage: any = function( eventData: any ): void { + if( settings.logging.net.relay.rawMessage ) { + console.log( + `Relay ${self.strRelayName}` + "\" outgoing-client socket \"" + + eventData.strSavedRemoteAddress + "\" raw message", eventData + ); + } + const joMessage = eventData.message; + if( settings.logging.net.relay.message ) { + console.log( + `Relay ${self.strRelayName}` + "\" outgoing-client socket \"" + + pipeIncoming.strSavedRemoteAddress + "\" message ", joMessage + ); + } + if( !pipeOutgoing ) { + throw new Error( `Relay ${self.strRelayName} is not completely initialized ` + + "and cannot transfer messages" ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "message", + { + relay: self, + socket: pipeIncoming, + remoteAddress: pipeIncoming.strSavedRemoteAddress.toString(), + isExternalSocket: false, + message: joMessage + } ) + ); + pipeIncoming.send( joMessage ); + if( self.isAutoFlushOutgoing ) + pipeIncoming.flush(); + }; + _offAllPipeEventListeners = function(): void { + if( _onIncomingPipeClose ) { + pipeIncoming.off( "close", _onIncomingPipeClose ); + _onIncomingPipeClose = null; + } + if( _onIncomingPipeError ) { + pipeIncoming.off( "error", _onIncomingPipeError ); + _onIncomingPipeError = null; + } + if( _onIncomingPipeMessage ) { + pipeIncoming.off( "message", _onIncomingPipeMessage ); + _onIncomingPipeMessage = null; + } + if( pipeOutgoing.relayClientSocket ) { + if( _onOutgoingPipeClose ) { + pipeOutgoing.off( "close", _onOutgoingPipeClose ); + _onOutgoingPipeClose = null; + } + if( _onOutgoingPipeError ) { + pipeOutgoing.off( "error", _onOutgoingPipeError ); + _onOutgoingPipeError = null; + } + if( _onOutgoingPipeMessage ) { + pipeOutgoing.off( "message", _onOutgoingPipeMessage ); + _onOutgoingPipeMessage = null; + } + pipeOutgoing.disconnect(); + pipeOutgoing.dispose(); + } + pipeIncoming.disconnect(); + pipeIncoming.dispose(); + }; + pipeIncoming.on( "close", _onIncomingPipeClose ); + pipeIncoming.on( "error", _onIncomingPipeError ); + pipeIncoming.on( "message", _onIncomingPipeMessage ); + + // 2) configure outgoing relay client pipe + pipeOutgoing.on( "close", _onOutgoingPipeClose ); + pipeOutgoing.on( "error", _onOutgoingPipeError ); + pipeOutgoing.on( "message", _onOutgoingPipeMessage ); + } + + dispose(): void { + this.isDisposing = true; + super.dispose(); + } + + flush(): void { + if( this.pipeIncoming ) + this.pipeIncoming.flush(); + if( this.pipeOutgoing ) + this.pipeOutgoing.flush(); + } +}; + +export class DirectPipe extends BasicSocketPipe { + socketType: string; + socketSubtype: string; + isConnected: boolean; + acceptor: any; + counterPipe: any; + strEndPoint: string; + clientPort: number; + url: string; + constructor ( counterPipe: any, isBroadcastOpenEvents: boolean ) { + super(); + isBroadcastOpenEvents = ( !!isBroadcastOpenEvents ); + this.socketType = "Direct"; + this.socketSubtype = "direct.not.initialized.yet"; + this.isConnected = false; + this.acceptor = null; + this.counterPipe = counterPipe ?? null; // set outside after this constructor call + this.strEndPoint = this.counterPipe + ? ( "2-" + this.counterPipe.strEndPoint ) + : ( "1-" + utils.randomDirectPipeID() ); + this.clientPort = this.counterPipe ? 2 : 1; + this.socketSubtype = "direct." + this.clientPort; + this.url = "direct_pipe://" + this.strEndPoint + ":" + this.clientPort; + if( this.counterPipe ) { + this.counterPipe.counterPipe = this; + this.isConnected = true; + this.counterPipe.isConnected = true; + if( isBroadcastOpenEvents ) { + const self = this; + const iv = setTimeout( function(): void { + clearTimeout( iv ); + self.dispatchEvent( new UniversalDispatcherEvent( + "open", { socket: self } ) ); + self.counterPipe.dispatchEvent( + new UniversalDispatcherEvent( + "open", { socket: self.counterPipe } ) ); + }, 0 ); + } + } + } + + dispose(): void { + this.performDisconnect(); + super.dispose(); + } + + handleServerDisposed(): void { // this method is for using in local client/server pipe pairs + this.performDisconnect(); + this.isConnected = false; + this.dispatchEvent( new UniversalDispatcherEvent( "close", { socket: this } ) ); + this.acceptor = null; + this.counterPipe = null; + this.clientPort = 0; + this.url = ""; + this.dispose(); + } + + performDisconnect(): void { + if( !this.isConnected ) + return; + this.isConnected = false; + if( this.acceptor ) + this.acceptor.unregisterClientByKey( this.clientPort ); + this.dispatchEvent( new UniversalDispatcherEvent( "close", { socket: this } ) ); + this.counterPipe.performDisconnect(); + this.acceptor = null; + this.counterPipe = null; + this.clientPort = 0; + this.url = ""; + } + + implSend( data: any ): void { + if( ( !this.isConnected ) || ( !this.counterPipe?.isConnected ) ) { + const s = "Cannot send messages to disconnected local server pipe"; + this.dispatchEvent( + new UniversalDispatcherEvent( "error", { socket: this, message: s.toString() } ) ); + throw new Error( s ); + } + const s = socketSentDataMarshall( data ); + const jo: any = socketReceivedDataReverseMarshall( s ); + this.counterPipe.receive( jo ); + } + + disconnect(): void { + this.performDisconnect(); + super.disconnect(); + } +}; + +export class LocalSocketServerPipe extends DirectPipe { + constructor( counterPipe: any, acceptor: any, clientPort: number ) { + super( counterPipe, false ); + this.socketType = "Local"; + this.socketSubtype = "server"; + this.isConnected = true; + this.acceptor = acceptor; + this.clientPort = cc.toInteger( clientPort ); + this.url = "local_server_pipe://" + acceptor.strEndPoint + ":" + clientPort; + this.acceptor.mapClients[clientPort.toString()] = this; + const self = this; + const iv = setTimeout( function(): void { + clearTimeout( iv ); + self.dispatchEvent( new UniversalDispatcherEvent( "open", { socket: self } ) ); + }, 0 ); + } + + dispose(): void { + super.dispose(); + } +}; + +export class LocalSocketServerAcceptor extends BasicServerAcceptor { + nextClientPort: number; + constructor ( strEndPoint: string ) { + super(); + this.socketType = "Local"; + this.nextClientPort = 1; + this.strEndPoint = + ( strEndPoint && typeof strEndPoint === "string" && strEndPoint.length > 0 ) + ? strEndPoint + : "default_local_endpoint"; + if( this.strEndPoint in gMapLocalServers ) { + const s = + "Cannot start local socket server on already listening \"" + + this.strEndPoint + "\" endpoint"; + this.dispatchEvent( new UniversalDispatcherEvent( + "error", { socket: this, message: s.toString() } ) ); + throw new Error( s ); + } + gMapLocalServers[this.strEndPoint] = this; + this.isListening = true; + const self = this; + const iv = setTimeout( function(): void { + clearTimeout( iv ); + self.dispatchEvent( + new UniversalDispatcherEvent( "open", { socket: self } ) ); + }, 0 ); + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.disposeNotifyClients(); + if( this.strEndPoint && + typeof this.strEndPoint === "string" && + this.strEndPoint.length > 0 + ) { + if( this.strEndPoint in gMapLocalServers ) + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete gMapLocalServers[this.strEndPoint]; + } + super.dispose(); + } +}; + +export class LocalSocketClientPipe extends DirectPipe { + constructor( strEndPoint: string ) { + super( null, false ); + this.socketType = "Local"; + this.socketSubtype = "client"; + this.isConnected = false; + this.clientPort = 0; + this.acceptor = null; + this.counterPipe = null; + this.strEndPoint = + ( strEndPoint && typeof strEndPoint === "string" && strEndPoint.length > 0 ) + ? strEndPoint + : "default_local_endpoint"; + if( !( this.strEndPoint in gMapLocalServers ) ) { + const s = + "Cannot connect to local socket server \"" + this.strEndPoint + + "\" endpoint, no such server"; + this.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { socket: this, message: s.toString() } ) ); + throw new Error( s ); + } + this.acceptor = gMapLocalServers[this.strEndPoint]; + this.clientPort = cc.toInteger( this.acceptor.nextClientPort ); + ++this.acceptor.nextClientPort; + this.url = "local_client_pipe://" + this.strEndPoint + ":" + this.clientPort; + this.isConnected = true; + const serverPipe = + new LocalSocketServerPipe( this, this.acceptor, cc.toInteger( this.clientPort ) ); + serverPipe.counterPipe = this; + this.counterPipe = serverPipe; + this.acceptor.mapClients[this.clientPort.toString()] = { + serverPipe, + clientPipe: this + }; + const self = this; + const iv = setTimeout( function(): void { + clearTimeout( iv ); + self.dispatchEvent( new UniversalDispatcherEvent( "open", { socket: self } ) ); + self.acceptor.dispatchEvent( + new UniversalDispatcherEvent( + "connection", + { socket: serverPipe, remoteAddress: self.url.toString() } ) + ); + }, 0 ); + } + + dispose(): void { + super.dispose(); + } +}; + +export class WebSocketServerPipe extends BasicSocketPipe { + clientNumber: number; + wsConnection: any; + remoteAddress: string; + _onWsClose: any; + _onWsError: any; + _onWsMessage: any; + _removeWsEventListeners: any; + constructor ( acceptor: any, wsConnection: any, remoteAddress: string ) { + super(); + this.socketType = "WS"; + this.socketSubtype = "server"; + const self = this; + this.isConnected = true; + this.acceptor = acceptor; + this.clientNumber = cc.toInteger( acceptor.nextClientNumber ); + this.clientPort = cc.toInteger( this.clientNumber ); + ++acceptor.nextClientNumber; + this.wsConnection = wsConnection; + this.remoteAddress = remoteAddress.toString(); + this.url = "ws_server_pipe(" + this.clientNumber + ")://" + remoteAddress; + this._onWsClose = function(): void { + self.dispatchEvent( + new UniversalDispatcherEvent( "close", { socket: self } ) ); + }; + this._onWsError = function( event: any ): void { + self.dispatchEvent( + new UniversalDispatcherEvent( "error", { socket: self, message: event } ) ); + }; + this._onWsMessage = function( event: any ): void { + self.receive( event.data ); + }; + this._removeWsEventListeners = function(): void { + if( self._onWsClose ) { + wsConnection.removeEventListener( "close", self._onWsClose ); + self._onWsClose = null; + } + if( self._onWsError ) { + wsConnection.removeEventListener( "error", self._onWsError ); + self._onWsError = null; + } + if( self._onWsMessage ) { + wsConnection.removeEventListener( "message", self._onWsMessage ); + self._onWsMessage = null; + } + }; + wsConnection.addEventListener( "close", this._onWsClose ); + wsConnection.addEventListener( "error", this._onWsError ); + wsConnection.addEventListener( "message", this._onWsMessage ); + this.acceptor.mapClients[this.clientPort.toString()] = this; + const iv = setTimeout( function(): void { + clearTimeout( iv ); + self.dispatchEvent( new UniversalDispatcherEvent( "open", { socket: self } ) ); + self.acceptor.dispatchEvent( + new UniversalDispatcherEvent( + "connection", + { socket: self, remoteAddress: remoteAddress.toString() } ) + ); + }, 0 ); + } + + dispose(): void { + this.performDisconnect(); + super.dispose(); + } + + handleServerDisposed(): void { + this.isConnected = false; + this.clientNumber = 0; + this.acceptor = null; + this.wsConnection = null; + this.url = ""; + this.remoteAddress = ""; + this.dispose(); + } + + performDisconnect(): void { + if( !this.isConnected ) + return; + this.isConnected = false; + if( this._removeWsEventListeners ) { + this._removeWsEventListeners(); + this._removeWsEventListeners = null; + } + if( this.wsConnection ) { + try { + this.wsConnection.terminate(); + } catch ( err ) { + console.warn( "Web socket server pipe termination error", err ); + } + this.wsConnection = null; + } + if( this.acceptor ) + this.acceptor.unregisterClientByKey( this.clientPort ); + this.clientNumber = 0; + this.acceptor = null; + this.url = ""; + this.remoteAddress = ""; + } + + implSend( data: any ): void { + if( ( !this.isConnected ) || ( !this.wsConnection ) ) { + const s = "Cannot send messages to disconnected web socket server pipe"; + this.dispatchEvent( + new UniversalDispatcherEvent( "error", { socket: this, message: s.toString() } ) ); + throw new Error( s ); + } + const s = socketSentDataMarshall( data ); + this.wsConnection.send( s ); + } + + disconnect(): void { + this.performDisconnect(); + super.disconnect(); + } + + implReceive( data: any ): void { + const jo: any = socketReceivedDataReverseMarshall( data ); + this.dispatchEvent( + new UniversalDispatcherEvent( "message", { socket: this, message: jo } ) ); + } +}; + +export class WebSocketServerAcceptor extends BasicServerAcceptor { + wsServer: any; + httpsModule: any; + constructor ( nTcpPort: null, key?: string, cert?: string ) { + super(); + this.socketType = "WS"; + this.wsServer = null; + if( key != null && key != undefined && typeof key === "string" && key.length > 0 && + cert != null && cert != undefined && typeof cert === "string" && cert.length > 0 + ) { + const server = httpsModule.createServer( { + key: key.toString(), + cert: cert.toString() + // , ca: ... + } ); + server.listen( nTcpPort ); + this.wsServer = new wsModule.WebSocketServer( { server } ); + } else + this.wsServer = new wsModule.WebSocketServer( { port: nTcpPort } ); + + const self = this; + self.wsServer.on( "connection", function( wsConnection: any, req: any ): void { + wsConnection.strSavedRemoteAddress = req?.connection?.remoteAddress?.toString(); + wsConnection.serverPipe = + new WebSocketServerPipe( self, wsConnection, req.connection.remoteAddress ); + } ); + this.isListening = true; + const iv = setTimeout( function(): void { + clearTimeout( iv ); + self.dispatchEvent( new UniversalDispatcherEvent( "open", { socket: self } ) ); + }, 0 ); + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.disposeNotifyClients(); + super.dispose(); + } +}; + +export class WebSocketClientPipe extends BasicSocketPipe { + wsConnection: any; + _onWsOpen: any; + _onWsClose: any; + _onWsError: any; + _onWsMessage: any; + urlWS: string | null; + _removeWsEventListeners: any; + constructor ( url: string | URL | null ) { + super(); + this.socketType = "WS"; + this.socketSubtype = "client"; + this.isConnected = false; + this.wsConnection = null; + this._onWsOpen = null; + this._onWsClose = null; + this._onWsError = null; + this._onWsMessage = null; + this.urlWS = + ( url ? url.toString() : "" ); + this.url = "ws_client_pipe-" + this.urlWS; + this.reconnect(); + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.performDisconnect(); + this.urlWS = null; + super.dispose(); + } + + implSend( data: any ): void { + if( ( !this.isConnected ) || ( !this.wsConnection ) ) { + const s = "Cannot send messages to disconnected web socket client pipe"; + this.dispatchEvent( + new UniversalDispatcherEvent( "error", { socket: this, message: s.toString() } ) ); + throw new Error( s ); + } + const s = socketSentDataMarshall( data ); + this.wsConnection.send( s ); + } + + reconnect(): void { + this.performDisconnect(); + this.wsConnect( this.urlWS ? this.urlWS.toString() : "" ); + } + + disconnect(): void { + this.performDisconnect(); + super.disconnect(); + } + + performDisconnect(): void { + if( !this.isConnected ) + return; + this.wsDisconnect(); + } + + wsConnectAttempt( url: string, reconnectAfterMilliseconds?: number, iv?: any ): boolean { + const self = this; + try { + if( this.isConnected || this.wsConnection ) + this.wsDisconnect(); + this.wsConnection = wsModule + ? new wsModule.WebSocket( + url, + { tlsOptions: { rejectUnauthorized: false } } + ) // server side + : new WebSocket( url ); // client side + this.url = url ? url.toString() : ""; + this._onWsOpen = function(): void { + self.isConnected = true; + self.dispatchEvent( + new UniversalDispatcherEvent( + "open", { socket: self } ) ); + }; + this._onWsClose = function( event: any ): void { + // alert( JSON.stringify( event ) ); + self.isConnected = false; + self.dispatchEvent( + new UniversalDispatcherEvent( + "close", { socket: self, message: event } ) ); + }; + this._onWsError = function( event: any ): void { + // alert( JSON.stringify( event ) ); + self.isConnected = false; + self.dispatchEvent( + new UniversalDispatcherEvent( + "error", { socket: self, message: event } ) ); + }; + this._onWsMessage = function( event: any ): void { + self.receive( event.data ); + }; + this._removeWsEventListeners = function(): void { + if( self._onWsOpen ) { + self.wsConnection.removeEventListener( "open", self._onWsOpen ); + self._onWsOpen = null; + } + if( self._onWsClose ) { + self.wsConnection.removeEventListener( "close", self._onWsClose ); + self._onWsClose = null; + } + if( self._onWsError ) { + self.wsConnection.removeEventListener( "error", self._onWsError ); + self._onWsError = null; + } + if( self._onWsMessage ) { + self.wsConnection.removeEventListener( "message", self._onWsMessage ); + self._onWsMessage = null; + } + }; + this.wsConnection.addEventListener( "open", this._onWsOpen ); + this.wsConnection.addEventListener( "close", this._onWsClose ); + this.wsConnection.addEventListener( "error", this._onWsError ); + this.wsConnection.addEventListener( "message", this._onWsMessage ); + if( iv ) + clearTimeout( iv ); + return true; + } catch ( err ) { + console.warn( "WS client connect error:", err ); + } + if( reconnectAfterMilliseconds != null && reconnectAfterMilliseconds != undefined ) { + if( reconnectAfterMilliseconds > 0 && ( !iv ) ) { + const iv = setTimeout( function(): void { + try { + if( self.wsConnectAttempt( url, reconnectAfterMilliseconds, iv ) ) + clearTimeout( iv ); + } catch ( err ) { + } + }, reconnectAfterMilliseconds ); + } + } + return false; + } + + wsConnect( url: string ): void { + if( url.length == 0 ) { + const s = "Cannot connect web socket server \"" + url + "\", bad url"; + this.dispatchEvent( + new UniversalDispatcherEvent( "error", { socket: this, message: s.toString() } ) ); + throw new Error( s ); + } + this.wsConnectAttempt( url, settings.net.ws.client.reconnectAfterMilliseconds, null ); + } + + wsDisconnect(): void { + if( this._removeWsEventListeners ) { + this._removeWsEventListeners(); + this._removeWsEventListeners = null; + } + if( this.wsConnection ) { + let bPass = false; let anyError = null; + try { + this.wsConnection.close(); + bPass = true; + } catch ( err ) { + anyError = err; + } + if( !bPass ) { + try { + this.wsConnection.terminate(); + bPass = true; + } catch ( err ) { + anyError = err; + } + } + if( !bPass ) + console.warn( "Web socket client pipe termination error", anyError ); + this.wsConnection = null; + } + this.isConnected = false; + this.url = ""; + } + + implReceive( data: any ): void { + const jo: any = socketReceivedDataReverseMarshall( data ); + this.dispatchEvent( + new UniversalDispatcherEvent( "message", { socket: this, message: jo } ) ); + } +}; + +export class RTCConnection extends EventDispatcher { + strSignalingServerURL: string | null; + idRtcParticipant: string | null; + wasIdentified: boolean; + iceComplete: any; + pc: any; + dc: any; + constructor ( strSignalingServerURL?: string, idRtcParticipant?: string ) { + super(); + this.strSignalingServerURL = utils.makeValidSignalingServerURL( strSignalingServerURL ); + this.idRtcParticipant = ( idRtcParticipant ? idRtcParticipant.toString() : utils.UUIDv4() ); + this.wasIdentified = false; + this.iceComplete = false; + this.pc = null; + this.dc = null; + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.closeDataChannel(); + this.closePeer(); + this.dc = null; + this.wasIdentified = false; + this.iceComplete = false; + this.idRtcParticipant = null; + super.dispose(); + } + + describe( strInstanceType?: string, arrAdditionalProps?: any[] ): string { + let strInstanceDescription = ( !strInstanceType ) + ? "participant" + : strInstanceType.toString(); + if( typeof this.idRtcParticipant === "string" && this.idRtcParticipant.length > 0 ) + strInstanceDescription += " " + this.idRtcParticipant; + const arrProps: any[] = []; + if( this.isDisposed ) + arrProps.push( "disposed" ); + if( this.wasIdentified ) + arrProps.push( "identified" ); + if( this.pc ) + arrProps.push( "pc" ); + if( this.dc ) + arrProps.push( "dc" ); + if( arrAdditionalProps != null && + arrAdditionalProps != undefined && + arrAdditionalProps.length > 0 + ) { + for( let i = 0; i < arrAdditionalProps.length; ++i ) + arrProps.push( arrAdditionalProps[i] ); + } + if( arrProps.length > 0 ) + strInstanceDescription += "(" + arrProps.join( ", " ) + ")"; + return strInstanceDescription; + } + + closeDataChannel(): void { + if( this.dc ) { + try { + this.dc.ondatachannel = null; + this.dc.close(); + if( settings.logging.net.rtc.closeDataChannel ) + console.warn( this.describe() + " did closed RTC data channel" ); + } catch ( err ) { + if( settings.logging.net.rtc.error ) + console.warn( this.describe() + " error closing RTC data channel:", err ); + } + this.dc = null; + this.dispatchEvent( + new UniversalDispatcherEvent( + "dataChannelClose", { detail: { actor: this } } ) ); + } + } + + closePeer(): void { + if( this.pc ) { + try { + this.pc.onicecandidate = null; + this.pc.oniceconnectionstatechange = null; + this.pc.close(); + if( settings.logging.net.rtc.closePeer ) + console.warn( this.describe() + " did closed RTC peer" ); + } catch ( err ) { + if( settings.logging.net.rtc.error ) + console.warn( this.describe() + " error closing RTC peer:", err ); + } + this.dispatchEvent( + new UniversalDispatcherEvent( + "peerClose", { detail: { actor: this } } ) ); + this.pc = null; + } + } + + onError( err: any ): void { + this.dispatchEvent( + new UniversalDispatcherEvent( + "rtcParticipantError", { detail: { actor: this, error: err } } ) ); + if( settings.logging.net.rtc.error ) + console.warn( " !!! " + this.describe() + " error:", err ); + this.closeDataChannel(); + this.closePeer(); + } + + send( data: any ): void { + const s = socketSentDataMarshall( data ); + if( !this.dc ) { + this.onError( `Attempt to send message to uninitialized RTC data channel: ${s}` ); + return; + } + try { + this.dc.send( s ); + } catch ( err: any ) { + this.onError( `Failed to send message to RTC data channel: ${err}` ); + } + } + + onDataChannelOpen( event: any ): void { + this.dispatchEvent( + new UniversalDispatcherEvent( + "dataChannelOpen", { detail: { actor: this } } ) ); + } + + onDataChannelClose( even: any ): void { + this.dispatchEvent( + new UniversalDispatcherEvent( + "dataChannelClose", { detail: { actor: this } } ) ); + } + + onDataChannelError( event: any ): void { + this.dispatchEvent( + new UniversalDispatcherEvent( + "dataChannelError", { detail: { actor: this } } ) ); + this.onError( "Data channel error " + event.toString() ); + } + + onDataChannelMessage( event: any ): void { + if( event.data.size ) { + if( settings.logging.net.rtc.error ) { + console.warn( + this.describe() + " will ignore file transfer message of size", event.data.size + ); + } + } else { + if( event.data.charCodeAt( 0 ) == 2 ) + return; + const data = JSON.parse( event.data ); + if( data.type === "file" ) { + if( settings.logging.net.rtc.error ) + console.warn( this.describe() + " will ignore file transfer message" ); + } else { + this.dispatchEvent( + new UniversalDispatcherEvent( + "dataChannelMessage", { detail: { actor: this, data } } ) ); + } + } + } + + onIceComplete( event: any ): void { + } + + onIceConnectionStateChange( event: any ): void { + // handler for self.pc.oniceconnectionstatechange, + // see https://developer.mozilla.org/en-US/docs/ + // Web/API/RTCPeerConnection/oniceconnectionstatechange + if( settings.logging.net.rtc.iceConnectionStateChange ) { + console.log( + "Participant \"" + this.idRtcParticipant + + "\" ICE connection state changed to \"" + + this.pc.iceConnectionState + "\", event is:", event + ); + } else if( settings.logging.net.rtc.iceConnectionStateName ) { + // similar to previous but prints only connection state name + console.log( + "Participant \"" + this.idRtcParticipant + + "\" ICE connection state changed to \"" + this.pc.iceConnectionState + "\"" + ); + } + if( this.pc.iceConnectionState === "failed" || + this.pc.iceConnectionState === "closed" || + this.pc.iceConnectionState === "disconnected" + ) { + this.onError( + "ICE connection state(oniceconnectionstatechange) changed to " + + this.pc.iceConnectionState + ); + } + } + + onIceGatheringStateChange( event: any ): void { + // handler for self.pc.onicegatheringstatechange - this is recommended to handle + // in a same way as oniceconnectionstatechange, + // see https://developer.mozilla.org/en-US/docs/ + // Web/API/RTCPeerConnection/onicegatheringstatechange + if( !this.pc ) { + console.log( + "WARNING: Participant \"" + this.idRtcParticipant + + "\" ICE gathering state changed event with no pc\", event is:", event + ); + return; + } + if( settings.logging.net.rtc.iceGatheringStateChange ) { + console.log( + "Participant \"" + this.idRtcParticipant + + "\" ICE gathering state changed to \"" + this.pc.iceGatheringState + + "\", event is:", event + ); + } else if( settings.logging.net.rtc.iceGatheringStateName ) { + // similar to previous but prints only gathering state name + console.log( + "Participant \"" + this.idRtcParticipant + + "\" ICE gathering state changed to \"" + + this.pc.iceGatheringState + "\"" + ); + } + if( this.pc.iceConnectionState === "failed" || + this.pc.iceConnectionState === "closed" || + this.pc.iceConnectionState === "disconnected" + ) { + this.onError( + "ICE connection state(onicegatheringstatechange) changed to " + + this.pc.iceConnectionState + ); + } + } + + onIceIdentifyResult( event: any ): void { + // handler for self.pc.onidentityresult, + // see https://developer.mozilla.org/en-US/docs/Web/API/RTCIdentityEvent + if( settings.logging.net.rtc.iceIceIdentifyResult ) { + if( "assertion" in event ) { + console.warn( + "Participant \"" + this.idRtcParticipant + + "\" ICE identify result event with new identity assertion (blob: '" + + event.assertion + "') has been generated." + ); + } else { + console.warn( + "Participant \"" + this.idRtcParticipant + + "\" ICE identify result event is:", event + ); + } + } + } + + onIceSignalingStateChange( event: any ): void { + // handler for self.pc.onsignalingstatechange, see + // https://developer.mozilla.org/en-US/docs/ + // Web/API/RTCPeerConnection/onsignalingstatechange + if( settings.logging.net.rtc.iceSignalingStateChange ) { + console.log( + "Participant \"" + this.idRtcParticipant + + "\" ICE signaling state changed to \"" + + ( ( this.pc && "signalingState" in this.pc ) ? this.pc.signalingState : "N/A" ) + + "\", event is:", event ); + } + } + + onIceNegotiationNeeded( event: any ): void { + // handler for self.pc.onnegotiationneeded, + // see https://developer.mozilla.org/en-US/docs/ + // Web/API/RTCPeerConnection/onnegotiationneeded + // TO-DO: improve this + if( settings.logging.net.rtc.iceNegotiationNeeded ) { + console.log( + "Participant \"" + this.idRtcParticipant + + "\" ICE negotiation needed event is:", event + ); + } + } +}; + +export class RTCActor extends RTCConnection { + idSomebodyCreator: string | null; + bWasImpersonated: boolean; + isCreator: boolean; + isJoiner: boolean; + offerOptions: any; + signalingOptions: any; + signalingPipe: any; + constructor ( + strSignalingServerURL: string, idRtcParticipant: string, + offerOptions: any, signalingOptions: any + ) { + super( strSignalingServerURL, idRtcParticipant ); + this.isDisposed = false; + this.idSomebodyCreator = null; + this.bWasImpersonated = false; + this.isCreator = false; + this.isJoiner = false; + this.offerOptions = { + optional: [], + // offer to the remote peer the opportunity to try to send audio + offerToReceiveAudio: false, + // offer to the remote peer the opportunity to try to send video + offerToReceiveVideo: false, + voiceActivityDetection: false, + iceRestart: false + }; + if( offerOptions ) { + this.offerOptions.offerToReceiveAudio = + !!( ( "offerToReceiveAudio" in offerOptions && offerOptions.offerToReceiveAudio ) ); + this.offerOptions.offerToReceiveVideo = + !!( ( "offerToReceiveVideo" in offerOptions && offerOptions.offerToReceiveVideo ) ); + this.offerOptions.voiceActivityDetection = + !!( ( "voiceActivityDetection" in offerOptions && + offerOptions.voiceActivityDetection ) ); + this.offerOptions.iceRestart = + !!( ( "iceRestart" in offerOptions && offerOptions.iceRestart ) ); + } + + this.signalingOptions = { + idCategory: settings.rtcSpace.defaultSpaceCategory.toString(), + idSpace: settings.rtcSpace.defaultSpaceName.toString() + }; + if( signalingOptions ) { + if( "idCategory" in signalingOptions && + typeof signalingOptions.idCategory === "string" && + signalingOptions.idCategory.length > 0 + ) + this.signalingOptions.idCategory = signalingOptions.idCategory.toString(); + if( "idSpace" in signalingOptions && + typeof signalingOptions.idSpace === "string" && + signalingOptions.idSpace.length > 0 + ) + this.signalingOptions.idSpace = signalingOptions.idSpace.toString(); + } + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.signalingPipeClose(); + this.idSomebodyCreator = null; + this.strSignalingServerURL = null; + this.bWasImpersonated = false; + super.dispose(); + } + + describe( strInstanceType?: string, arrAdditionalProps?: any[] ): string { + strInstanceType = + ( strInstanceType == null || + strInstanceType == undefined || + ( typeof strInstanceType !== "string" ) || + strInstanceType.length == 0 ) + ? ( this.isCreator ? "creator" : ( this.isJoiner ? "joiner" : "actor" ) ) + : strInstanceType; + return super.describe( strInstanceType, arrAdditionalProps ); + } + + onError( err: any ): void { + super.onError( err ); + } + + signalingPipeOpen(): void { + try { + const self = this; + self.signalingPipeClose(); + self.dispatchEvent( + new UniversalDispatcherEvent( + "signalingWillStart", { detail: { actor: this } } ) ); + self.signalingPipe = new WebSocketClientPipe( self.strSignalingServerURL ); + self.signalingPipe.on( + "open", function( eventData: any ): void { + self.signalingPipeOnOpen( eventData ); + } ); + self.signalingPipe.on( + "close", function( eventData: any ): void { + self.signalingPipeOnClose( eventData ); + } ); + self.signalingPipe.on( + "error", function( eventData: any ): void { + self.signalingPipeOnError( eventData ); + } ); + self.signalingPipe.on( + "message", function( eventData: any ): void { + self.signalingPipeOnRawMessage( eventData ); + } ); + self.dispatchEvent( + new UniversalDispatcherEvent( + "signalingDidStarted", { detail: { actor: this } } ) ); + } catch ( err ) { + if( settings.logging.net.signaling.error ) + console.warn( this.describe() + " error starting signaling pipe:", err ); + this.onError( err ); + } + } + + signalingPipeClose(): void { + if( this.signalingPipe ) { + try { + if( settings.logging.net.signaling.disconnect ) + console.warn( this.describe() + " will close signaling pipe" ); + this.signalingPipe.offAll(); + this.signalingPipe.disconnect(); + if( settings.logging.net.signaling.disconnect ) + console.warn( this.describe() + " did closed signaling pipe" ); + } catch ( err ) { + if( settings.logging.net.signaling.error ) + console.warn( this.describe() + " error closing signaling pipe:", err ); + } + this.signalingPipe = null; + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingClosed", { detail: { actor: this } } ) ); + } + } + + signalingPipeOnOpen( eventData: any ): void { + try { + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingOpened", { detail: { actor: this } } ) ); + if( settings.logging.net.signaling.connect ) { + console.log( + "+++ " + this.describe() + " did connected to " + this.strSignalingServerURL + ); + } + const joImpersonateMessage = { + id: utils.randomCallID(), + method: "signalingImpersonate", + idCategory: this.signalingOptions.idCategory.toString(), + idSpace: this.signalingOptions.idSpace.toString(), + idRtcParticipant: this.idRtcParticipant ? this.idRtcParticipant.toString() : "", + role: this.isCreator ? "creator" : "joiner" + }; + if( settings.logging.net.signaling.message ) + console.log( " <<< " + this.describe() + " message out", joImpersonateMessage ); + this.signalingPipe.send( joImpersonateMessage ); + } catch ( err ) { + if( settings.logging.net.signaling.error ) { + console.warn( + this.describe() + " error sending impersonation to signaling pipe:", err + ); + } + this.onError( err ); + } + } + + signalingPipeOnClose( eventData: any ): void { + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingPipeClose", { detail: { actor: this } } ) ); + if( settings.logging.net.signaling.disconnect ) { + console.warn( + " !!! " + this.describe() + " signaling pipe closed for " + + this.strSignalingServerURL + ); + } + this.signalingPipeClose(); + } + + signalingPipeOnError( eventData: any ): void { + // alert( JSON.stringify( eventData ) ); + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingPipeError", { detail: { actor: this, error: eventData } } ) ); + if( settings.logging.net.signaling.error ) { + console.warn( + " !!! " + this.describe() + " signaling pipe error for " + + this.strSignalingServerURL + ", error is:", eventData + ); + } + this.onError( eventData ); + this.signalingPipeClose(); + } + + signalingPipeOnRawMessage( eventData: any ): void { + try { + if( settings.logging.net.signaling.rawMessage ) { + console.log( + " >>> " + this.describe() + " raw signaling message received", eventData ); + } + const joMessage = eventData.message; + if( settings.logging.net.signaling.message ) { + console.log( + " >>> " + this.describe() + " signaling message received", joMessage ); + } + this.signalingPipeOnMessage( joMessage ); + } catch ( err ) { + if( settings.logging.net.signaling.error ) + console.warn( "Error handling raw message in " + this.describe() + ":", err ); + this.onError( err ); + } + } + + signalingPipeOnMessage( joMessage: any ): void { + switch ( joMessage.method ) { + case "signalingImpersonate": + if( joMessage.error == null ) { + // OKay, impersonated + this.bWasImpersonated = true; + if( settings.logging.net.signaling.generic ) { + console.log( + "Success, " + this.describe() + " impersonated on signaling server" + ); + } + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingPassedImpersonation", { detail: { actor: this } } ) ); + this.onImpersonationComplete(); + } else { + if( settings.logging.net.signaling.error ) { + console.warn( + " >>> " + this.describe() + " signaling impersonation error", + joMessage.error + ); + } + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingFailedImpersonation", + { detail: { actor: this, error: joMessage.error } } ) + ); + this.onError( joMessage.error ); + } + break; + default: + if( settings.logging.net.signaling.error ) { + console.warn( + " >>> " + this.describe() + " unhandled signaling message", + joMessage + ); + } + break; + } // switch( joMessage.method ) + } + + onImpersonationComplete(): void { } + // generic implementation should never be called + onOtherSideIdentified( idSomebodyOtherSide: any, idOffer: any ): void { } +}; + +export class RTCServerPeer extends RTCConnection { + rtcCreator: any; + idSomebodyOtherSide: string | null; + idOffer: number; + tsOfferCreated: any; + isPublishing: boolean; + isSignalingNegotiation: boolean; + isPublishTimeout: boolean; + isSignalingNegotiationTimeout: boolean; + timerPublishing: any; + timerSignalingNegotiation: any; + timeToPublishMilliseconds: any; + timeToSignalingNegotiationMilliseconds: number; + peerConfiguration: any; + peerAdditionalOptions: any; + localMediaStream: any; + isOfferPublishedOnSignalingServer: boolean; + offerOptions: any; + serverPipe: any; + constructor ( + rtcCreator: any, + timeToPublishMilliseconds: number, timeToSignalingNegotiationMilliseconds: number, + peerConfiguration: any, peerAdditionalOptions: any, localMediaStream?: any + ) { + super(); + this.rtcCreator = rtcCreator; + this.idSomebodyOtherSide = null; + this.idOffer = this.rtcCreator.idOfferNext++; + this.tsOfferCreated = null; + if( settings.logging.net.signaling.offerRegister ) + console.log( "Register offer", this.idOffer, "(RTCServerPeer constructor)" ); + this.rtcCreator.mapServerOffers[cc.toInteger( this.idOffer )] = this; + this.isPublishing = false; + this.isSignalingNegotiation = false; + this.isPublishTimeout = false; + this.isSignalingNegotiationTimeout = false; + this.timerPublishing = null; + this.timerSignalingNegotiation = null; + this.timeToPublishMilliseconds = timeToPublishMilliseconds || + settings.net.rtc.timeToPublishMilliseconds; + this.timeToSignalingNegotiationMilliseconds = timeToSignalingNegotiationMilliseconds || + settings.net.rtc.timeToSignalingNegotiationMilliseconds; + this.peerConfiguration = + ( peerConfiguration && typeof peerConfiguration === "object" ) + ? peerConfiguration + : settings.net.rtc.peerConfiguration; + this.peerAdditionalOptions = + ( peerAdditionalOptions && typeof peerAdditionalOptions === "object" ) + ? peerAdditionalOptions + : settings.net.rtc.peerAdditionalOptions; + this.localMediaStream = + ( localMediaStream != null && localMediaStream != undefined && + typeof localMediaStream === "object" ) + ? localMediaStream + : null; + this.isOfferPublishedOnSignalingServer = false; + this.initPeer(); + this.publish(); + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.publishCancel(); + this.signalingNegotiationCancel(); + if( this.rtcCreator ) { + if( this.idOffer && this.idOffer in this.rtcCreator.mapServerOffers ) { + if( settings.logging.net.signaling.offerUnregister ) + console.log( "Unregister offer", this.idOffer, "(RTCServerPeer dispose)" ); + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.rtcCreator.mapServerOffers[this.idOffer]; + } + this.idOffer = 0; + } + this.idOffer = 0; + if( this.idSomebodyOtherSide != null ) { + if( this.idSomebodyOtherSide in this.rtcCreator.mapServerPeers ) + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.rtcCreator.mapServerPeers[this.idSomebodyOtherSide]; + this.idSomebodyOtherSide = null; + } + this.rtcCreator = null; + this.tsOfferCreated = null; + super.dispose(); + } + + describe( strInstanceType?: string, arrAdditionalProps?: any[] ): string { + strInstanceType = + ( strInstanceType == null || strInstanceType == undefined || + ( typeof strInstanceType !== "string" ) || strInstanceType.length == 0 ) + ? "server-peer" + : strInstanceType; + return super.describe( strInstanceType, arrAdditionalProps ); + } + + initPeer(): void { + if( this.isDisposed ) + return; + const self = this; + if( self.pc ) + return; + self.pc = + new webRtcModule.RTCPeerConnection( + self.peerConfiguration, self.peerAdditionalOptions ); + if( self.localMediaStream ) { + for( const track of self.localMediaStream.getTracks() ) + self.pc.addTrack( track, self.localMediaStream ); + } else { + self.dc = + self.pc.createDataChannel( + settings.net.rtc.dataChannel.label, settings.net.rtc.dataChannel.opts ); + self.dc.addEventListener( + "open", function( event: any ): void { self.onDataChannelOpen( event ); } ); + self.dc.addEventListener( + "close", function( event: any ): void { self.onDataChannelClose( event ); } ); + self.dc.addEventListener( + "error", function( event: any ): void { self.onDataChannelError( event ); } ); + self.dc.addEventListener( + "message", function( event: any ): void { self.onDataChannelMessage( event ); } ); + } + } + + publishCancel(): void { + if( !this.isPublishing ) + return; + this.isOfferPublishedOnSignalingServer = false; + this.isPublishing = false; + if( this.timerPublishing ) { + clearTimeout( this.timerPublishing ); + this.timerPublishing = null; + } + this.signalingNegotiationCancel(); // mutual cancel + } + + signalingNegotiationCancel(): void { + if( !this.isSignalingNegotiation ) + return; + this.isSignalingNegotiation = false; + if( this.timerSignalingNegotiation ) { + clearTimeout( this.timerSignalingNegotiation ); + this.timerSignalingNegotiation = null; + } + this.publishCancel(); // mutual cancel + } + + publish(): void { + if( this.isDisposed || this.isPublishing || this.isSignalingNegotiation || + ( !this.rtcCreator?.signalingPipe ) ) + return; + const self = this; + self.isPublishing = true; + if( self.timeToPublishMilliseconds > 0 ) { + self.isSignalingNegotiation = false; + self.timerPublishing = setTimeout( function(): void { + self.publishCancel(); + self.signalingNegotiationCancel(); + self.isPublishTimeout = true; + if( settings.logging.net.signaling.publishTimeout ) { + console.warn( + " !!! " + self.describe() + " offer publish timeout " + + self.timeToPublishMilliseconds + " milliseconds reached" + ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "publishTimeout", + { detail: { participant: self } } ) + ); + if( self.rtcCreator ) { + self.rtcCreator.dispatchEvent( + new UniversalDispatcherEvent( "publishTimeout", + { detail: { participant: self } } ) + ); + } + }, self.timeToPublishMilliseconds ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "publishStart", { detail: { participant: self } } ) ); + self.pc.oniceconnectionstatechange = + function( event: any ): void { self.onIceConnectionStateChange( event ); }; + self.pc.onicegatheringstatechange = + function( event: any ): void { self.onIceGatheringStateChange( event ); }; + self.pc.onidentityresult = + function( event: any ): void { self.onIceIdentifyResult( event ); }; + self.pc.onsignalingstatechange = + function( event: any ): void { self.onIceSignalingStateChange( event ); }; + self.pc.onnegotiationneeded = + function( event: any ): void { self.onIceNegotiationNeeded( event ); }; + self.pc.createOffer( self.offerOptions ).then( + function( offerDescription: any ) { + // success + self.tsOfferCreated = new Date(); + if( settings.logging.net.signaling.offer ) { + console.log( + " <<< " + self.describe() + " offer created at " + + utils.formatDateTime( self.tsOfferCreated ) + + " with description:", offerDescription + ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "offerCreated", { detail: { participant: self } } ) ); + self.pc.setLocalDescription( offerDescription ).then( + function(): void { + // success + if( settings.logging.net.signaling.localDescription ) { + console.log( + " <<< " + self.describe() + " local description set:", + offerDescription + ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "localDescriptionSet", + { detail: { participant: self } } ) + ); + self.pc.onicecandidate = function( event: any ): void { + self.iceComplete = true; + self.onIceComplete( event ); + }; // onicecandidate + }, function( err: any ): void { + // error of setLocalDescription + self.publishCancel(); + self.signalingNegotiationCancel(); + self.onError( "Failed to set local description: " + err ); + } ); + }, function( err: any ): void { + self.publishCancel(); + self.signalingNegotiationCancel(); + // error of createOffer + self.onError( "Failed to create offer:" + err ); + } ); + } + + onOtherSideIdentified( idSomebodyOtherSide: any ): void { + this.publishCancel(); + this.signalingNegotiationCancel(); + this.idSomebodyOtherSide = idSomebodyOtherSide ? idSomebodyOtherSide.toString() : ""; + this.wasIdentified = true; + this.dispatchEvent( + new UniversalDispatcherEvent( + "identified", + { + detail: { + participant: this, + idSomebodyOtherSide: + idSomebodyOtherSide ? idSomebodyOtherSide.toString() : "" + } + } ) + ); + } + + onError( err: any ): void { + if( this.rtcCreator ) { + this.rtcCreator.onRtcPeerError( this, err ); + if( this.idOffer && this.idOffer in this.rtcCreator.mapServerOffers ) { + if( settings.logging.net.signaling.offerUnregister ) { + console.log( + "Unregister offer", this.idOffer, "due to RTCServerPeer error:", err ); + } + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.rtcCreator.mapServerOffers[this.idOffer]; + } + this.idOffer = 0; + } + if( this.idSomebodyOtherSide != null ) { + if( this.idSomebodyOtherSide in this.rtcCreator.mapServerPeers ) + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.rtcCreator.mapServerPeers[this.idSomebodyOtherSide]; + this.idSomebodyOtherSide = null; + } + super.onError( err ); + } + + onImpersonationCompleteForCreator(): void { // specific for server peer + if( settings.logging.net.signaling.creatorImpersonationComplete ) + console.log( "Creator impersonation complete" ); + } + + publishOfferOnSignalingServer(): void { + const self = this; + // eslint-disable-next-line no-useless-catch + try { + if( settings.logging.net.signaling.candidate ) + console.log( " <<< " + self.describe() + " got candidate", event ); + if( settings.logging.net.signaling.candidate ) + console.log( " <<< " + self.describe() + " got candidate", event ); + if( !self.rtcCreator.signalingPipe ) + throw new Error( "no connection to signaling server" ); + const joPublishOfferMessage = { + id: utils.randomCallID(), + method: "signalingPublishOffer", + offer: self.pc.localDescription, + idSomebodyCreator: self.rtcCreator.idRtcParticipant.toString(), + idOffer: cc.toInteger( self.idOffer ?? 0 ) + }; + if( settings.logging.net.signaling.message ) { + console.log( + " <<< " + self.describe() + " signaling message out", + joPublishOfferMessage + ); + } + self.rtcCreator.signalingPipe.send( joPublishOfferMessage ); + self.publishCancel(); + self.dispatchEvent( + new UniversalDispatcherEvent( + "signalingNegotiationStart", { detail: { participant: self } } ) ); + if( self.timeToSignalingNegotiationMilliseconds > 0 ) { + self.isSignalingNegotiation = true; + self.timerSignalingNegotiation = setTimeout( function(): void { + self.publishCancel(); + self.signalingNegotiationCancel(); + self.isSignalingNegotiationTimeout = true; + if( settings.logging.net.signaling.signalingNegotiationTimeout ) { + console.warn( + " !!! " + self.describe() + " signaling negotiation timeout " + + self.timeToSignalingNegotiationMilliseconds + " milliseconds reached" + ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "signalingNegotiationTimeout", + { detail: { participant: self } } ) + ); + if( self.rtcCreator ) { + self.rtcCreator.dispatchEvent( + new UniversalDispatcherEvent( + "signalingNegotiationTimeout", + { detail: { participant: self } } ) + ); + } + }, self.timeToSignalingNegotiationMilliseconds ); + } + } catch ( err ) { + throw err; + } + } + + onIceComplete( event: any ): void { + super.onIceComplete( event ); + const self = this; + try { + if( event.candidate == null || + settings.net.rtc.fastPublishMode.serverPeer + ) { + if( !self.isOfferPublishedOnSignalingServer ) { + self.isOfferPublishedOnSignalingServer = true; + self.publishOfferOnSignalingServer(); + } + } + if( event.candidate != null ) { + if( settings.logging.net.signaling.candidateWalk ) { + console.log( + " <<< " + self.describe() + " got candidate", event + ); + } + } + } catch ( err: any ) { + self.publishCancel(); + self.signalingNegotiationCancel(); + self.onError( "Failed to process ICE candidate: " + err ); + } + } +}; + +export class RTCCreator extends RTCActor { + idOfferNext?: number; + mapServerOffers: any; + mapServerPeers: any; + constructor ( + strSignalingServerURL: string, idRtcParticipant: string, + offerOptions: any, signalingOptions: any + ) { + super( strSignalingServerURL, idRtcParticipant, offerOptions, signalingOptions ); + const self = this; + self.idOfferNext = 1; + self.isCreator = true; + self.mapServerOffers = { }; // idOffer -> RTCServerPeer + self.mapServerPeers = { }; // idSomebodyOtherSide -> RTCServerPeer + self.signalingPipeOpen(); + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + for( const [ idOfferWalk, rtcPeerWalk ] of Object.entries( this.mapServerOffers ) ) { + const rtcPeer: any = rtcPeerWalk; + if( settings.logging.net.signaling.offerUnregister ) + console.log( "Unregister offer", idOfferWalk, "(one of all, RTCCreator dispose)" ); + rtcPeer.dispose(); + } + for( const [ /* idSomebodyOtherSideWalk */, rtcPeerWalk ] of + Object.entries( this.mapServerPeers ) ) { + const rtcPeer: any = rtcPeerWalk; + rtcPeer.dispose(); + } + this.mapServerOffers = { }; + super.dispose(); + } + + describe( strInstanceType?: string, arrAdditionalProps?: any[] ): string { + strInstanceType = + ( strInstanceType == null || strInstanceType == undefined || + ( typeof strInstanceType !== "string" ) || strInstanceType.length == 0 ) + ? "rtc-creator" + : strInstanceType; + return super.describe( strInstanceType, arrAdditionalProps ); + } + + onOtherSideIdentified( + idSomebodyOtherSide: any, idOffer: any ): void { // server peer got result + if( settings.logging.net.signaling.impersonate ) { + console.log( + this.describe() + " did identified other side RTC joiner \"" + + idSomebodyOtherSide + "\" via offer ID " + idOffer.toString() + ); + } + if( !( idOffer in this.mapServerOffers ) ) { + const strError = "not a registered pending offer(onOtherSideIdentified)"; + if( settings.logging.net.signaling.error ) { + console.warn( + " >>> " + this.describe() + + " came across with incorrect other side identification for *somebody*", + idSomebodyOtherSide, "and offer ID", idOffer, ":", strError ); + } + this.onError( strError ); + return; + } + const rtcPeer = this.mapServerOffers[idOffer]; + if( settings.logging.net.signaling.offerUnregister ) { + console.log( + "Unregister offer", idOffer, "(onOtherSideIdentified in RTCCreator)" + ); + } + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.mapServerOffers[idOffer]; + this.mapServerPeers[idSomebodyOtherSide.toString()] = rtcPeer; + rtcPeer.onOtherSideIdentified( idSomebodyOtherSide.toString() ); + } + + onRtcPeerError( rtcPeer: any, err: any ): void { + if( settings.logging.net.rtc.error ) + console.warn( " !!! " + this.describe() + " rtc peer error", err ); + this.dispatchEvent( + new UniversalDispatcherEvent( + "rtcPeerError", + { detail: { actor: this, peer: rtcPeer, error: err } } ) + ); + } + + signalingPipeOnMessage( joMessage: any ): void { + const self = this; + switch ( joMessage.method ) { + case "signalingPublishOffer": + if( joMessage.error == null ) { + // OKay, creator offer published + if( settings.logging.net.signaling.offer ) + console.log( "Success, " + this.describe() + " offer published (step 1)" ); + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingPassedOfferPublish", { detail: { actor: this } } ) ); + } else { + if( settings.logging.net.signaling.error ) { + console.warn( + " !!! " + this.describe() + " signaling offer publishing (step 1) error", + joMessage.error ); + } + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingFailedOfferPublish", + { detail: { actor: this, error: joMessage.error } } ) + ); + this.onError( joMessage.error ); + } + break; + case "signalingPublishAnswer": // server peer got result + if( joMessage.error == null ) { + const idSomebodyOtherSide = joMessage.idSomebody_joiner.toString(); + const idOffer = cc.toInteger( joMessage.idOffer ); + if( !( idOffer in this.mapServerOffers ) ) { + const strError = "not a registered pending offer(signalingPublishAnswer)"; + if( settings.logging.net.signaling.error ) { + console.warn( + " !!! " + this.describe() + + " came across with incorrect " + + "signalingPublishAnswer message for *somebody*", + idSomebodyOtherSide, "and offer ID", idOffer, ":", strError + ); + } + this.onError( strError ); + return; + } + const rtcPeer = this.mapServerOffers[idOffer]; + // OKay, finally got answer from candida + if( settings.logging.net.signaling.generic ) { + console.log( + "Success, " + this.describe() + " got answer from candidate (step 3)" + ); + } + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingPassedPublishAnswer", + { + detail: { + actor: this, + idSomebodyOtherSide: idSomebodyOtherSide.toString(), + idOffer + } + } ) + ); + const answer = joMessage.answer; + if( settings.logging.net.signaling.offer ) + console.log( " >>> " + self.describe() + " got answer:", answer ); + const answerDescription = new webRtcModule.RTCSessionDescription( answer ); + if( settings.logging.net.signaling.offer ) { + console.log( + " >>> " + self.describe() + " got answer description:", + answerDescription + ); + } + if( rtcPeer.pc.signalingState != "have-local-offer" ) { + if( settings.logging.net.signaling.offerSkipPublishedAnswer ) { + console.warn( + " >>> " + self.describe() + " in \"" + rtcPeer.pc.signalingState + + "\" state will skip setting remote description from answer", + answerDescription + ); + } + return; + } + rtcPeer.pc.setRemoteDescription( answerDescription ).then( + function(): void { + // success + if( settings.logging.net.signaling.remoteDescription ) { + console.log( + " >>> " + self.describe() + "did set remote description:", + answerDescription + ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "remoteDescriptionSet", + { detail: { participant: self } } ) + ); + self.onOtherSideIdentified( + idSomebodyOtherSide, idOffer ); // server peer got result + }, function( err: any ): void { + // error + self.onError( "Failed to set remote description: " + err ); + } ); + } else { + if( settings.logging.net.signaling.error ) { + console.warn( + " !!! " + this.describe() + + " error getting candidate answer (step 1) error", + joMessage.error ); + } + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingFailedPublishAnswer", + { detail: { actor: this, error: joMessage.error } } ) + ); + this.onError( joMessage.error ); + } + break; + default: + super.signalingPipeOnMessage( joMessage ); + break; + } // switch( joMessage.method ) + } + + send( data: any ): void { // implementation in RTCCreator does send to all + try { + const s = socketSentDataMarshall( data ); + for( const [ /* idSomebodyOtherSideWalk */, rtcPeerWalk ] + of Object.entries( this.mapServerPeers ) ) { + const rtcPeer: any = rtcPeerWalk; + try { + rtcPeer.send( s ); + } catch ( err ) { + this.onRtcPeerError( rtcPeer, err ); + } + } + } catch ( err ) { + this.onError( err ); + } + } + + onImpersonationComplete(): void { + super.onImpersonationComplete(); + for( const [ /* idOfferWalk */, rtcPeerWalk ] + of Object.entries( this.mapServerOffers ) ) { + const rtcPeer: any = rtcPeerWalk; + rtcPeer.onImpersonationCompleteForCreator(); + } + for( const [ /* idSomebodyOtherSideWalk */, rtcPeerWalk ] + of Object.entries( this.mapServerPeers ) ) { + const rtcPeer: any = rtcPeerWalk; + rtcPeer.onImpersonationCompleteForCreator(); + } + } +}; + +export class RTCJoiner extends RTCActor { + idSomebodyOtherSide: any; + idOffer: number; + tsAnswerCreated: any; + isAnswerPublishedOnSignalingServer: boolean; + peerConfiguration: any; + peerAdditionalOptions: any; + constructor ( + strSignalingServerURL: string, idRtcParticipant: string, offerOptions: any, + signalingOptions: any, peerConfiguration: any, peerAdditionalOptions: any + ) { + super( strSignalingServerURL, idRtcParticipant, offerOptions, signalingOptions ); + this.idSomebodyOtherSide = null; + this.idOffer = 0; + this.isJoiner = true; + this.tsAnswerCreated = null; + this.isAnswerPublishedOnSignalingServer = false; + this.signalingPipeOpen(); + this.peerConfiguration = + ( peerConfiguration && typeof peerConfiguration === "object" ) + ? peerConfiguration + : settings.net.rtc.peerConfiguration; + this.peerAdditionalOptions = + ( peerAdditionalOptions && typeof peerAdditionalOptions === "object" ) + ? peerAdditionalOptions + : settings.net.rtc.peerAdditionalOptions; + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.idSomebodyOtherSide = null; + this.idOffer = 0; + this.tsAnswerCreated = null; + this.isAnswerPublishedOnSignalingServer = false; + super.dispose(); + } + + describe( strInstanceType?: string, arrAdditionalProps?: any[] ): string { + strInstanceType = + ( strInstanceType == null || + strInstanceType == undefined || + ( typeof strInstanceType !== "string" ) || + strInstanceType.length == 0 ) + ? "rtc-joiner" + : strInstanceType; + return super.describe( strInstanceType, arrAdditionalProps ); + } + + initPeer(): void { + if( this.isDisposed ) + return; + const self = this; + if( self.pc ) + return; + self.pc = + new webRtcModule.RTCPeerConnection( + self.peerConfiguration, self.peerAdditionalOptions ); + self.pc.addEventListener( "track", function( event: any ): void { + self.dispatchEvent( + new UniversalDispatcherEvent( + "trackAvailable", + { detail: { participant: self, event } } ) ); + } ); + self.pc.oniceconnectionstatechange = + function( event: any ): void { self.onIceConnectionStateChange( event ); }; + self.pc.onicegatheringstatechange = + function( event: any ): void { self.onIceGatheringStateChange( event ); }; + self.pc.onidentityresult = + function( event: any ): void { self.onIceIdentifyResult( event ); }; + self.pc.onsignalingstatechange = + function( event: any ): void { self.onIceSignalingStateChange( event ); }; + self.pc.onnegotiationneeded = + function( event: any ): void { self.onIceNegotiationNeeded( event ); }; + self.pc.ondatachannel = function( event: any ): void { + self.dispatchEvent( + new UniversalDispatcherEvent( + "dataChannelAvailable", + { detail: { participant: self, event } } ) + ); + const dataChannel = event.channel || event; + self.dc = dataChannel; + self.dc.addEventListener( + "open", + function( event: any ): void { self.onDataChannelOpen( event ); } ); + self.dc.addEventListener( + "close", + function( event: any ): void { self.onDataChannelClose( event ); } ); + self.dc.addEventListener( + "error", + function( event: any ): void { self.onDataChannelError( event ); } ); + self.dc.addEventListener( + "message", + function( event: any ): void { self.onDataChannelMessage( event ); } ); + }; + self.pc.onicecandidate = function( event: any ): void { + self.iceComplete = true; + self.onIceComplete( event ); + try { + if( !self.signalingPipe ) { + if( self.dc ) + return; // already connected, ignore (Firefox fix) + throw new Error( "no connection to signaling server" ); + } + if( !self.isAnswerPublishedOnSignalingServer ) { + self.publishSignalingAnswer( event ); + self.isAnswerPublishedOnSignalingServer = true; + } + if( event.candidate != null ) { + if( settings.logging.net.signaling.candidateWalk ) { + console.log( + " <<< " + self.describe() + " got candidate", + event + ); + } + } + } catch ( err: any ) { + self.onError( + "Failed to process ICE candidate: " + err + ); + } + }; // onicecandidate + } + + publishSignalingAnswer( event: any ): void { + const self = this; + // eslint-disable-next-line no-useless-catch + try { + if( event.candidate == null || + settings.net.rtc.fastPublishMode.joiner + ) { + if( settings.logging.net.signaling.candidate ) { + console.log( + " <<< " + self.describe() + " got candidate", + event + ); + } + if( !self.signalingPipe ) + throw new Error( "no connection to signaling server" ); + const joPublishAnswerMessage = { + id: utils.randomCallID(), + method: "signalingPublishAnswer", + answer: self.pc.localDescription, + idRtcParticipant: + self.idRtcParticipant ? self.idRtcParticipant.toString() : "", + idSomebodyCreator: + self.idSomebodyCreator ? self.idSomebodyCreator.toString() : "", + idOffer: self.idOffer + }; + if( settings.logging.net.signaling.message ) { + console.log( + " <<< " + self.describe() + + " signaling client message out", + joPublishAnswerMessage + ); + } + self.signalingPipe.send( joPublishAnswerMessage ); + } + } catch ( err ) { + throw err; + } + } + + delayedInitPeer(): void { + if( this.bWasImpersonated ) + this.initPeer(); + } + + onImpersonationComplete(): void { + super.onImpersonationComplete(); + const joFetchOfferMessage = { + id: utils.randomCallID(), + method: "signalingFetchOffer" + }; + if( settings.logging.net.signaling.message ) { + console.log( + " <<< " + this.describe() + " signaling client message out", + joFetchOfferMessage + ); + } + this.signalingPipe.send( joFetchOfferMessage ); + } + + onIceComplete( event: any ): void { + super.onIceComplete( event ); + } + + onOtherSideIdentified( + idSomebodyOtherSide: any, idOffer: any ): void { // client peer got result + if( settings.logging.net.signaling.impersonate ) { + console.log( + this.describe() + " did identified other side RTC creator \"" + + idSomebodyOtherSide + "\" via offer ID " + idOffer.toString() + ); + } + this.idSomebodyOtherSide = idSomebodyOtherSide.toString(); + this.idOffer = idOffer; + this.wasIdentified = true; + this.dispatchEvent( + new UniversalDispatcherEvent( + "identified", + { + detail: { + participant: this, + idSomebodyOtherSide: idSomebodyOtherSide.toString() + } + } ) ); + } + + signalingPipeOnMessage( joMessage: any ): void { + const self = this; + switch ( joMessage.method ) { + case "signalingFetchOffer": + if( joMessage.error == null ) { + // OKay, fetched offer from creator + this.delayedInitPeer(); + this.idSomebodyCreator = joMessage.idSomebodyCreator.toString(); + const idSomebodyOtherSide = joMessage.idSomebodyCreator.toString(); + const idOffer = cc.toInteger( joMessage.idOffer ); + if( settings.logging.net.signaling.generic ) { + console.log( + "Success, " + this.describe() + " fetched offer from creator (step 2)" + ); + } + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingPassedFetchOffer", + { + detail: { + actor: this, + idSomebodyOtherSide: idSomebodyOtherSide.toString(), + idOffer + } + } ) ); + const offer = joMessage.offer; + if( settings.logging.net.signaling.offer ) + console.log( " <<< " + self.describe() + " got offer:", offer ); + const offerDescription = new webRtcModule.RTCSessionDescription( offer ); + if( settings.logging.net.signaling.offer ) { + console.log( + " <<< " + self.describe() + " got offer description:", offerDescription + ); + } + this.pc.setRemoteDescription( offerDescription ).then( + function(): void { + // success + if( settings.logging.net.signaling.remoteDescription ) { + console.log( + " <<< " + self.describe() + "did set remote description:", + offerDescription ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "remoteDescriptionSet", + { detail: { participant: self } } ) ); + self.pc.createAnswer( self.offerOptions ).then( + function( answerDescription: any ): void { + // success + self.tsAnswerCreated = new Date(); + if( settings.logging.net.signaling.answer ) { + console.log( + " <<< " + self.describe() + "did created answer at " + + utils.formatDateTime( self.tsAnswerCreated ) + + " with description:", answerDescription ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "answerCreated", + { detail: { participant: self } } ) ); + self.pc.setLocalDescription( answerDescription ).then( + function(): void { + // success + if( settings.logging.net.signaling.localDescription ) { + console.log( + " <<< " + self.describe() + + " local description set:", answerDescription ); + } + self.dispatchEvent( + new UniversalDispatcherEvent( + "localDescriptionSet", + { detail: { participant: self } } ) ); + self.onOtherSideIdentified( + idSomebodyOtherSide, + idOffer ); // client peer got result + }, function( err: any ): void { + // error of setLocalDescription + self.onError( + "Failed to set local description " + + "(while fetching offer for \"" + + idSomebodyOtherSide + "\"): " + + err.toString() ); + } ); + }, function( err: any ): void { + // error of createAnswer + self.onError( + "Failed to create answer (while fetching offer for \"" + + idSomebodyOtherSide + "\"): " + err ); + } ); + }, function( err: any ): void { + // error of setLocalDescription + self.onError( + "Failed to set remote description: (while fetching offer for \"" + + idSomebodyOtherSide + "\"): " + err ); + } ); + } else { + if( settings.logging.net.signaling.error ) { + console.warn( + " !!! " + this.describe() + + " signaling offer publishing (step 1) error", joMessage.error ); + } + this.dispatchEvent( + new UniversalDispatcherEvent( + "signalingFailedFetchOffer", + { detail: { actor: this, error: joMessage.error } } ) ); + this.onError( joMessage.error ); + } + break; + default: + super.signalingPipeOnMessage( joMessage ); + break; + } // switch( joMessage.method ) + } +}; + +export class WebRTCServerPipe extends BasicSocketPipe { + clientNumber?: number; + rtcPeer: any; + strSignalingServerURL?: string; + constructor ( acceptor: any, rtcPeer: any, strSignalingServerURL: string ) { + super(); + const self = this; + self.socketType = "WebRTC"; + self.socketSubtype = "server"; + self.isConnected = true; + self.acceptor = acceptor; + self.clientNumber = cc.toInteger( acceptor.nextClientNumber ); + self.clientPort = cc.toInteger( self.clientNumber ?? 0 ); + ++acceptor.nextClientNumber; + self.rtcPeer = rtcPeer; + self.strSignalingServerURL = + utils.makeValidSignalingServerURL( strSignalingServerURL ); + self.url = "rtc_server_pipe(" + self.clientNumber + ")://" + strSignalingServerURL; + self.rtcPeer.on( "dataChannelOpen", function( jo: any ): void { + self.isConnected = true; + self.acceptor.mapClients[self.clientPort.toString()] = self; + self.dispatchEvent( new UniversalDispatcherEvent( "open", { socket: self } ) ); + self.acceptor.dispatchEvent( + new UniversalDispatcherEvent( + "connection", + { socket: self, strSignalingServerURL: strSignalingServerURL.toString() } ) ); + } ); + self.rtcPeer.on( "dataChannelMessage", function( jo: any ): void { + self.receive( jo.detail.data ); + } ); + self.rtcPeer.on( "rtcParticipantError", function( jo: any ): void { + self.isConnected = false; + self.dispatchEvent( + new UniversalDispatcherEvent( "error", { socket: self, message: jo } ) ); + } ); + self.rtcPeer.on( "dataChannelError", function( jo: any ): void { + self.isConnected = false; + self.dispatchEvent( + new UniversalDispatcherEvent( "error", { socket: self, message: jo } ) ); + } ); + self.rtcPeer.on( "dataChannelClose", function( jo: any ): void { + self.isConnected = false; + self.dispatchEvent( + new UniversalDispatcherEvent( "close", { socket: self, message: jo } ) ); + } ); + self.rtcPeer.on( "peerClose", function( jo: any ): void { + self.isConnected = false; + self.dispatchEvent( + new UniversalDispatcherEvent( "close", { socket: self, message: jo } ) ); + } ); + } + + dispose(): void { + this.performDisconnect(); + super.dispose(); + } + + handleServerDisposed(): void { + this.performDisconnect(); + this.isConnected = false; + this.clientNumber = 0; + this.acceptor = null; + this.rtcPeer = null; + this.url = ""; + this.strSignalingServerURL = ""; + // super.handleServerDisposed(); + this.dispose(); + } + + performDisconnect(): void { + if( !this.isConnected ) + return; + this.isConnected = false; + if( this.acceptor ) + this.acceptor.unregisterClientByKey( this.clientPort ); + if( this.rtcPeer ) { + this.rtcPeer.offAll(); + this.rtcPeer = null; + } + this.clientNumber = 0; + this.acceptor = null; + this.url = ""; + this.strSignalingServerURL = ""; + } + + implSend( data: any ): void { + if( ( !this.isConnected ) || ( !this.rtcPeer ) ) { + const err = "Cannot send messages to disconnected WebRTC socket server pipe"; + this.onError( err ); + throw new Error( err ); + } + const s = socketSentDataMarshall( data ); + this.rtcPeer.send( s ); + } + + // eslint-disable-next-line n/handle-callback-err + onError( err: any ): void { + } + + disconnect(): void { + this.performDisconnect(); + super.disconnect(); + } + + implReceive( data: any ): void { + const jo: any = socketReceivedDataReverseMarshall( data ); + this.dispatchEvent( + new UniversalDispatcherEvent( "message", { socket: this, message: jo } ) ); + } +}; + +export class WebRTCServerAcceptor extends BasicServerAcceptor { + strSignalingServerURL: string; + idRtcParticipant: string; + offerOptions: any; + signalingOptions: any; + peerConfiguration: any; + peerAdditionalOptions: any; + maxActiveOfferCount: number; + mapPendingOffers: any; + timeToPublishMilliseconds: number; + timeToSignalingNegotiationMilliseconds: number; + rtcCreator: any; + isConnected?: false; + constructor ( + strSignalingServerURL: string, idRtcParticipant: any, offerOptions: any, + signalingOptions: any, maxActiveOfferCount?: number, timeToPublishMilliseconds?: number, + timeToSignalingNegotiationMilliseconds?: number, + peerConfiguration?: any, peerAdditionalOptions?: any + ) { + super(); + this.strSignalingServerURL = utils.makeValidSignalingServerURL( strSignalingServerURL ); + this.idRtcParticipant = idRtcParticipant ? idRtcParticipant.toString() : utils.UUIDv4(); + this.offerOptions = offerOptions || null; + this.signalingOptions = signalingOptions || null; + this.peerConfiguration = + ( peerConfiguration && typeof peerConfiguration === "object" ) + ? peerConfiguration + : settings.net.rtc.peerConfiguration; + this.peerAdditionalOptions = + ( peerAdditionalOptions && typeof peerAdditionalOptions === "object" ) + ? peerAdditionalOptions + : settings.net.rtc.peerAdditionalOptions; + this.socketType = "WebRTC"; + this.maxActiveOfferCount = maxActiveOfferCount ?? settings.net.rtc.maxActiveOfferCount; + if( this.maxActiveOfferCount < 1 ) + this.maxActiveOfferCount = 1; + this.mapPendingOffers = { }; // idOffer -> RTCServerPeer + this.timeToPublishMilliseconds = timeToPublishMilliseconds ?? + settings.net.rtc.timeToPublishMilliseconds; + this.timeToSignalingNegotiationMilliseconds = timeToSignalingNegotiationMilliseconds ?? + settings.net.rtc.timeToSignalingNegotiationMilliseconds; + this.rtcCreator = + new RTCCreator( + this.strSignalingServerURL.toString(), + this.idRtcParticipant.toString(), + this.offerOptions, + this.signalingOptions ); + this.isListening = true; + const self = this; + this.rtcCreator.on( "signalingPassedImpersonation", function( eventData: any ): void { + self.updateAllPendingOffers(); + self.dispatchEvent( + new UniversalDispatcherEvent( + "signalingPassedImpersonation", + { detail: { acceptor: self } } ) ); + } ); + this.rtcCreator.on( "signalingFailedImpersonation", function( eventData: any ): void { + self.dispatchEvent( + new UniversalDispatcherEvent( + "signalingFailedImpersonation", + { detail: { acceptor: self } } ) ); + } ); + this.rtcCreator.on( "error", function( eventData: any ): void { + self.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { + detail: { + acceptor: self, + eventData, + errorType: "rtcCreatorError" + } + } ) ); + } ); + this.rtcCreator.on( "close", function( eventData: any ): void { + self.dispatchEvent( + new UniversalDispatcherEvent( + "close", + { detail: { acceptor: self, eventData } } ) ); + } ); + self.rtcCreator.on( "signalingPipeError", function( jo: any ): void { + self.isConnected = false; + self.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { socket: self, message: jo, errorType: "signalingPipeError" } ) ); + } ); + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.removeAllPendingOffers(); + if( this.rtcCreator ) { + this.rtcCreator.dispose(); + this.rtcCreator = null; + } + this.disposeNotifyClients(); + super.dispose(); + } + + addPendingOffer(): void { + if( this.isDisposed ) + return; + const rtcPeer = + new RTCServerPeer( + this.rtcCreator, this.timeToPublishMilliseconds, + this.timeToSignalingNegotiationMilliseconds, + this.peerConfiguration, this.peerAdditionalOptions ); + const self = this; + rtcPeer.on( "identified", function( event: any ): void { + if( rtcPeer.isDisposing || rtcPeer.isDisposed ) + return; + if( settings.logging.net.signaling.generic ) { + console.log( + self.rtcCreator.describe() + " is now identified peer", + event.detail.idSomebodyOtherSide ); + } + rtcPeer.serverPipe = + new WebRTCServerPipe( self, rtcPeer, self.strSignalingServerURL ); + self.detachPendingOffer( rtcPeer.idOffer ); + self.dispatchEvent( + new UniversalDispatcherEvent( "identified", { detail: { peer: rtcPeer } } ) ); + self.updateAllPendingOffers(); + } ); + rtcPeer.on( "localDescriptionSet", function( event: any ): void { + self.dispatchEvent( + new UniversalDispatcherEvent( + "peerLocalDescriptionSet", + { detail: { acceptor: self, peerEvent: event } } ) ); + } ); + const onTimeoutHandler = function(): void { + self.disposePendingOffer( rtcPeer.idOffer ); + self.updateAllPendingOffers(); + }; + rtcPeer.on( "publishTimeout", onTimeoutHandler ); + rtcPeer.on( "signalingNegotiationTimeout", onTimeoutHandler ); + rtcPeer.on( "signalingNegotiationStart", function(): void { + self.updateAllPendingOffers(); + } ); + + const retranslateError = function( eventData: any ): void { + self.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { + detail: { + acceptor: self, + rtcPeer, + eventData, + errorType: "rtcPeerError" + } + } ) ); + }; + rtcPeer.on( "error", retranslateError ); + rtcPeer.on( "rtcPeerError", retranslateError ); + + this.mapPendingOffers[rtcPeer.idOffer] = rtcPeer; + } + + detachPendingOffer( idOffer: any ): void { + if( idOffer in this.mapPendingOffers ) + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.mapPendingOffers[idOffer]; + } + + disposePendingOffer( idOffer: any ): void { + if( idOffer in this.mapPendingOffers ) { + const rtcPeer = this.mapPendingOffers[idOffer]; + rtcPeer.dispose(); + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.mapPendingOffers[idOffer]; + } + } + + removeAllPendingOffers(): void { + for( const [ /* idOfferWalk */, rtcPeerWalk ] + of Object.entries( this.rtcCreator.mapServerPeers ) ) { + const rtcPeer: any = rtcPeerWalk; + const serverPipe = rtcPeer.serverPipe; + serverPipe.dispose(); + } + this.rtcCreator.mapServerPeers = { }; + for( const [ /* idOfferWalk */, rtcPeerWalk ] + of Object.entries( this.rtcCreator.mapPendingOffers ) ) { + const rtcPeer: any = rtcPeerWalk; + rtcPeer.dispose(); + } + this.mapPendingOffers = { }; + } + + updateAllPendingOffers(): void { + if( this.isDisposed ) + return; + for( let n = Object.keys( this.mapPendingOffers ).length; + n < this.maxActiveOfferCount; + ++n ) + this.addPendingOffer(); + } +}; + +export class WebRTCClientPipe extends BasicSocketPipe { + strSignalingServerURL: string | null; + idRtcParticipant: string; + offerOptions: any; + signalingOptions: any; + peerConfiguration: any; + peerAdditionalOptions: any; + rtcPeer: any; + isAutoCloseSignalingPipeOnDataChannelOpen: boolean; + constructor ( + strSignalingServerURL: string, idRtcParticipant: string, offerOptions: any, + signalingOptions: any, peerConfiguration: any, peerAdditionalOptions: any + ) { + super(); + this.strSignalingServerURL = utils.makeValidSignalingServerURL( strSignalingServerURL ); + this.idRtcParticipant = idRtcParticipant ? idRtcParticipant.toString() : utils.UUIDv4(); + this.offerOptions = offerOptions || null; + this.signalingOptions = signalingOptions || null; + this.peerConfiguration = + ( peerConfiguration && typeof peerConfiguration === "object" ) + ? peerConfiguration + : settings.net.rtc.peerConfiguration; + this.peerAdditionalOptions = + ( peerAdditionalOptions && typeof peerAdditionalOptions === "object" ) + ? peerAdditionalOptions + : settings.net.rtc.peerAdditionalOptions; + this.socketType = "WebRTC"; + this.socketSubtype = "client"; + this.isConnected = false; + this.rtcPeer = null; + this.isAutoCloseSignalingPipeOnDataChannelOpen = + ( !!( settings.net.rtc.isAutoCloseSignalingPipeOnDataChannelOpen ) ); + this.url = "rtc_client_pipe-" + this.strSignalingServerURL; + this.reconnect(); + } + + dispose(): void { + if( this.isDisposed ) + return; + this.isDisposing = true; + this.performDisconnect(); + this.strSignalingServerURL = null; + super.dispose(); + } + + implSend( data: any ): void { + if( ( !this.isConnected ) || ( !this.rtcPeer ) ) { + const s = "Cannot send messages to disconnected WebRTC socket client pipe"; + this.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { socket: this, message: s.toString(), errorType: "dataSendError" } ) ); + throw new Error( s ); + } + const s = socketSentDataMarshall( data ); + this.rtcPeer.send( s ); + } + + reconnect(): void { + this.performDisconnect(); + this.rtcConnect( this.strSignalingServerURL ? this.strSignalingServerURL.toString() : "" ); + } + + disconnect(): void { + this.performDisconnect(); + super.disconnect(); + } + + performDisconnect(): void { + if( !this.isConnected ) + return; + this.rtcDisconnect(); + } + + rtcConnect( strSignalingServerURL: string ): void { + if( strSignalingServerURL.length == 0 ) { + const s = "Cannot connect signaling server \"" + strSignalingServerURL + "\", bad url"; + this.dispatchEvent( + new UniversalDispatcherEvent( + "error", + { socket: this, message: s.toString(), errorType: "badSignalingServerURL" } ) ); + throw new Error( s ); + } + const self = this; + while( true ) { + try { + if( self.isConnected || self.rtcPeer ) + self.rtcDisconnect(); + self.rtcPeer = + new RTCJoiner( + strSignalingServerURL.toString(), self.idRtcParticipant.toString(), + self.offerOptions, self.signalingOptions, + self.peerConfiguration, self.peerAdditionalOptions + ); // client side + self.strSignalingServerURL = + utils.makeValidSignalingServerURL( strSignalingServerURL ); + self.rtcPeer.on( "identified", function( event: any ): void { + if( settings.logging.net.signaling.generic ) { + console.log( + self.rtcPeer.describe() + " is now identified peer", + event.detail.idSomebodyOtherSide + ); + } + } ); + self.rtcPeer.on( "dataChannelOpen", function( jo: any ): void { + self.isConnected = true; + self.dispatchEvent( + new UniversalDispatcherEvent( "open", { socket: self } ) + ); + if( self.isAutoCloseSignalingPipeOnDataChannelOpen ) { + if( settings.logging.net.signaling.disconnect ) { + console.warn( + self.rtcPeer.describe() + + " will auto-close signaling pipe" + + "(inside socket \"dataChannelOpen\" handler)" + ); + } + self.rtcPeer.signalingPipeClose(); + } + } ); + self.rtcPeer.on( "dataChannelMessage", function( jo: any ): void { + self.receive( jo.detail.data ); + } ); + self.rtcPeer.on( "rtcParticipantError", function( jo: any ): void { + self.isConnected = false; + self.dispatchEvent( new UniversalDispatcherEvent( + "error", + { + socket: self, + message: jo, + errorType: "rtcParticipantError" + } ) + ); + } ); + self.rtcPeer.on( "dataChannelError", function( jo: any ): void { + self.isConnected = false; + self.dispatchEvent( new UniversalDispatcherEvent( + "error", + { + socket: self, + message: jo, + errorType: "dataChannelError" + } ) + ); + } ); + self.rtcPeer.on( "dataChannelClose", function( jo: any ): void { + self.isConnected = false; + self.dispatchEvent( new UniversalDispatcherEvent( + "close", + { socket: self, message: jo } ) ); + } ); + self.rtcPeer.on( "signalingPipeError", function( jo: any ): void { + self.isConnected = false; + self.dispatchEvent( new UniversalDispatcherEvent( + "error", + { + socket: self, + message: jo, + errorType: "signalingPipeError" + } ) + ); + } ); + return; + } catch ( err ) { + console.warn( "WebRTC client connect error:", err ); + continue; + } + } + } + + rtcDisconnect(): void { + if( this.rtcPeer ) { + this.rtcPeer.offAll(); + this.rtcPeer.dispose(); + this.rtcPeer = null; + } + this.isConnected = false; + this.url = ""; + } + + implReceive( data: any ): void { + const jo: any = socketReceivedDataReverseMarshall( data ); + this.dispatchEvent( + new UniversalDispatcherEvent( + "message", + { socket: this, message: jo } ) ); + } +}; diff --git a/src/socketServer.ts b/src/socketServer.ts new file mode 100644 index 00000000..d5ea0e17 --- /dev/null +++ b/src/socketServer.ts @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE COOL SOCKET + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file socketServer.ts + * @copyright SKALE Labs 2019-Present + */ + +import { EventDispatcher, UniversalDispatcherEvent } from "./eventDispatcher.js"; +import * as utils from "./socketUtils.js"; +import * as log from "./log.js"; + +export class SocketServer extends EventDispatcher { + log: any; + acceptor: any; + mapApiHandlers: any; + mapAcceptedPipes: any; + isLogAcceptedSocket?: boolean; + isLogSocketErrors?: boolean; + isLogSocketTraffic?: boolean; + isLogSocketTrafficRaw?: boolean; + constructor ( acceptor: any ) { + super(); + if( acceptor == null || acceptor == undefined || typeof acceptor !== "object" ) + throw new Error( "Cannot create server on bad acceptor" ); + const self = this; + self.log = console.log; + self.acceptor = acceptor; + self.mapApiHandlers = {}; + self.mapAcceptedPipes = { }; + self.isLogAcceptedSocket = false; + self.isLogSocketErrors = true; + self.isLogSocketTraffic = false; + self.isLogSocketTrafficRaw = false; + acceptor.on( "connection", function( eventData: any ): void { + const socket = eventData.socket; + if( ( !( "remoteAddress" in eventData ) ) || + eventData.remoteAddress == null || + eventData.remoteAddress == undefined ) + socket.strSavedRemoteAddress = socket.constructor.name; + else { + socket.strSavedRemoteAddress = + eventData.remoteAddress ? eventData.remoteAddress.toString() : ""; + } + if( self.isLogAcceptedSocket ) { + self.log( log.fmtInformation( "New socket {url} was {bright}", + socket.strSavedRemoteAddress, "accepted" ) ); + } + self.mapAcceptedPipes[socket] = { }; + let _offAllPipeEventListeners: any = null; + let _onPipeClose: any = function(): void { + if( self.isLogAcceptedSocket ) { + self.log( log.fmtInformation( "Socket {url} was {bright}", + socket.strSavedRemoteAddress, "closed" ) ); + } + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete self.mapAcceptedPipes[socket]; + }; + let _onPipeError: any = function( eventData: any ): void { + if( self.isLogSocketErrors ) { + self.log( log.fmtError( "Socket {url} error {err}", + socket.strSavedRemoteAddress, eventData ) ); + } + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete self.mapAcceptedPipes[socket]; + }; + let _onPipeMessage: any = function( eventData: any ): void { + if( self.isLogSocketTrafficRaw ) { + self.log( log.fmtInformation( "Socket {url} did received {sunny} {}", + socket.strSavedRemoteAddress, "raw-message", eventData ) ); + } + const joMessage = eventData.message; + if( self.isLogAcceptedSocket ) { + self.log( log.fmtInformation( "Socket {url} did received {sunny} {}", + socket.strSavedRemoteAddress, "JSON-message", joMessage ) ); + } + let joAnswer: any = null; + let isFlush = false; + try { + if( joMessage.method in self.mapApiHandlers ) { + joAnswer = utils.prepareAnswerJSON( joMessage ); + joAnswer = self.mapApiHandlers[joMessage.method]( + joMessage, joAnswer, eventData, socket ); + if( joAnswer ) + isFlush = true; + } else { + joAnswer = utils.prepareAnswerJSON( joMessage ); + joAnswer.error = "Unhandled message"; + joAnswer.joMessage = joMessage; // send it back )) + if( self.isLogSocketTraffic ) { + self.log( log.fmtError( "Socket {url} had unhandled message {}", + socket.strSavedRemoteAddress, joMessage ) ); + } + isFlush = true; + } + } catch ( err: any ) { + if( self.isLogSocketErrors ) { + self.log( log.fmtError( + "Server method {} RPC exception: {err}, stack is: {stack}", + joMessage.method, err, err ) ); + } + joAnswer = utils.prepareAnswerJSON( joMessage ); + joAnswer.error = err ? err.toString() : "N/A error"; + } + + if( joAnswer != null && joAnswer != undefined ) { + if( typeof joAnswer.error === "string" && joAnswer.error.length > 0 ) { + if( self.isLogSocketErrors ) { + self.log( log.fmtError( "Socket {url} will send {sunny} {err}", + socket.strSavedRemoteAddress, "error-answer", joAnswer ) ); + } + } else { + if( self.isLogSocketTraffic ) { + self.log( log.fmtError( "Socket {url} will send {sunny} {err}", + socket.strSavedRemoteAddress, "answer", joAnswer ) ); + } + } + socket.send( joAnswer, isFlush ); + } + }; + _offAllPipeEventListeners = function(): void { + if( _onPipeClose ) { + socket.off( "close", _onPipeClose ); + _onPipeClose = null; + } + if( _onPipeError ) { + socket.off( "error", _onPipeError ); + _onPipeError = null; + } + if( _onPipeMessage ) { + socket.off( "message", _onPipeMessage ); + _onPipeMessage = null; + } + socket.disposeImpersonatedEntries(); + }; + socket.on( "close", _onPipeClose ); + socket.on( "error", _onPipeError ); + socket.on( "message", _onPipeMessage ); + } ); + this.dispatchEvent( + new UniversalDispatcherEvent( "initialized", { detail: { ref: this } } ) ); + } + + dispose(): void { + this.isDisposing = true; + super.dispose(); + } +}; diff --git a/src/socketSettings.ts b/src/socketSettings.ts new file mode 100644 index 00000000..51d64e8b --- /dev/null +++ b/src/socketSettings.ts @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE COOL SOCKET + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file socketSettings.ts + * @copyright SKALE Labs 2019-Present + */ + +const settings: any = { + rtcSpace: { + defaultSpaceName: "default space", + defaultSpaceCategory: "default category" + }, + net: { + hostname: "localhost", + secure: false, + ports: { + http: 8080, // 80, 443, 8080 + ws: 17171, + signaling: 17172 + }, + pipe: { + maxAccumulatedMessagesCount: 30 + }, + ws: { + client: { + reconnectAfterMilliseconds: 100 + } + }, + rtc: { + arrKnownIceServers: [ + // see: https://gist.github.com/mondain/b0ec1cf5f60ae726202e + // and https://gist.github.com/zziuni/3741933 + // see: https://stackoverflow.com/questions + // /20068944/webrtc-stun-stun-l-google-com19302 + // see: https://gist.github.com/yetithefoot/7592580 + // even more to see: + // https://gist.github.com/sagivo + // /3a4b2f2c7ac6e1b5267c2f1f59ac6c6b + "stun:stun.1.google.com:19302", + "stun:stun.2.google.com:19302", + "stun:stun.3.google.com:19302", + "stun:stun.4.google.com:19302", + "stun:stun.5.google.com:19302", // where is the end? + "stun:stun.l.google.com:19302", + "stun:stun1.l.google.com:19302", + "stun:stun2.l.google.com:19302", + "stun:stun3.l.google.com:19302", + "stun:stun4.l.google.com:19302", + "stun:stun5.l.google.com:19302", // where is the end? + "stun:stun.gmx.net", + "stun:stun.sipgate.net", + "stun:stun.sipgate.net:10000", + "stun:stun.phoneserve.com", + "stun:stun.counterpath.net", + "stun:stun.12connect.com:3478", + "stun:stun.xten.com", + "stun:stun01.sipphone.com", + "stun:stun.ekiga.net", + "stun:stun.fwdnet.net", + "stun:stun.ideasip.com", + "stun:stun.iptel.org", + "stun:stun.schlund.de", + "stun:stun.voiparound.com", + "stun:stun.voipbuster.com", + "stun:stun.voipstunt.com", + "stun:stun.voxgratia.org" + ], + peerConfiguration: { + iceServers: [ { + urls: "stun:some.ip.address.here:3478", + username: "some.user.name", + credential: "some.password" + } ] + }, + peerAdditionalOptions: { + optional: [ { DtlsSrtpKeyAgreement: true } ] + }, + dataChannel: { + label: "genericDataChannel", + opts: { reliable: true, ordered: true } + }, + maxActiveOfferCount: 10, + // networkLayer.WebRTCClientPipe only + isAutoCloseSignalingPipeOnDataChannelOpen: true, + // 0 - no timeout, 300000 = 5 minutes, 60000 = 1 minute + timeToPublishMilliseconds: 0, + // 0 - no timeout, 10000 = 10 seconds to identify by WebRTC + timeToSignalingNegotiationMilliseconds: 0, + offerDiscovery: { + periodMilliseconds: 1000, + stepCount: 20 + }, + fastPublishMode: { + serverPeer: true, + joiner: true + } + } + }, + logging: { + net: { + socket: { + flush: false, + flushOne: false, + flushBlock: false, + flushCount: false, + flushMethodStats: false, + accumulate: false, + send: false, + receive: false, + receiveBlock: false, + receiveCount: false, + receiveMethodStats: false + }, + signaling: { + generic: false, + connect: true, + disconnect: true, + error: true, + rawMessage: false, + message: false, + impersonate: false, + publishOffer: false, + objectLifetime: true, + offer: false, + answer: false, + localDescription: false, + remoteDescription: false, + candidate: false, + candidateWalk: false, + publishTimeout: true, + signalingNegotiationTimeout: true, + offerDiscoveryStepFail: true, + offerRegister: false, + offerUnregister: false, + offerSkipPublishedAnswer: false, + creatorImpersonationComplete: false + }, + rtc: { + generic: false, + error: true, + closePeer: true, + closeDataChannel: true, + iceConnectionStateChange: false, + iceConnectionStateName: false, + iceGatheringStateChange: false, + iceGatheringStateName: false, + iceIceIdentifyResult: false, + iceSignalingStateChange: false, + iceNegotiationNeeded: false + }, + server: { + connect: true, + disconnect: true, + error: true, + rawMessage: false, + message: false, + impersonate: true, + weaponShot: false, + collisionDetection: true + }, + client: { + space: { attach: true, detach: true }, + connect: true, + disconnect: true, + error: true, + rawMessage: false, + message: false, + impersonate: true + }, + relay: { + connect: true, + disconnect: true, + error: true, + rawMessage: false, + message: false + } + } + } +}; + +export { settings }; diff --git a/src/socketUtils.ts b/src/socketUtils.ts new file mode 100644 index 00000000..08b4c551 --- /dev/null +++ b/src/socketUtils.ts @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE COOL SOCKET + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file socketUtils.ts + * @copyright SKALE Labs 2019-Present + */ + +import { settings } from "./socketSettings.js"; + +export const UUIDv4 = function(): string { + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function( c ) { + const r = Math.random() * 16 | 0; const v = c == "x" ? r : ( r & 0x3 | 0x8 ); + return v.toString( 16 ); + } ); +}; + +export const getRandomInt = function( nMax: number ): number { + return Math.floor( Math.random() * Math.floor( nMax ) ); +}; + +export const randomFixedInteger = function( length: number ): number { + return Math.floor( + Math.pow( 10, length - 1 ) + + Math.random() * ( Math.pow( 10, length ) - Math.pow( 10, length - 1 ) - 1 ) ); +}; + +export const randomStringABC = function( length: number, arrCharacters: string ): string { + if( length <= 0 || arrCharacters.length == 0 ) + return ""; + let s = ""; + for( let i = 0; i < length; ++i ) + s += arrCharacters.charAt( Math.floor( Math.random() * arrCharacters.length ) ); + return s; +}; + +export const randomString = function( + length: number, + isABC?: boolean, isDigits?: boolean, isSpecChr?: boolean, isPunctuation?: boolean +): string { // by default only isABC=true + if( length <= 0 ) + return ""; + isABC = ( isABC == null || isABC == undefined ) + ? true + : ( !!isABC ); + isDigits = ( isDigits == null || isDigits == undefined ) + ? false + : ( !!isDigits ); + isSpecChr = ( isSpecChr == null || isSpecChr == undefined ) + ? false + : ( !!isSpecChr ); + isPunctuation = ( isPunctuation == null || isPunctuation == undefined ) + ? false + : ( !!isPunctuation ); + let arrCharacters = ""; + if( isABC ) + arrCharacters += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + if( isDigits ) + arrCharacters += "0123456789"; + if( isSpecChr ) + arrCharacters += "(){}[]~!?@#$%^&*_+-='\"/\\"; + if( isPunctuation ) + arrCharacters += ",.:;"; + if( arrCharacters.length == 0 ) + return ""; + return randomStringABC( length, arrCharacters ); +}; + +export const randomHexString = function( length: number ): string { + // length in characters, not bytes, each byte is 2 characters + const arrCharacters = "0123456789abcdef"; + return randomStringABC( length, arrCharacters ); +}; + +export const replaceAll = function( str: string, find: string, replace: string ): string { + return str.replace( new RegExp( find, "g" ), replace ); +}; + +export const simpleEscapeString = function( s?: any ): string { + if( s == null || s == undefined || typeof s !== "string" ) + return s; + s = replaceAll( s, "&", "&" ); + s = replaceAll( s, "<", "<" ); + s = replaceAll( s, ">", ">" ); + s = replaceAll( s, " ", " " ); + return s; +}; + +export const abstractUniqueID = function(): string { + const id = replaceAll( UUIDv4(), "-", "" ).toLowerCase(); + return id; +}; + +export const isEven = function( n: number ): boolean { + return n % 2 == 0; +}; +export const isOdd = function( n: number ): boolean { + return Math.abs( n % 2 ) == 1; +}; + +const gCountOfCallIdDigits: number = 10; +export const randomCallID = function(): string { + const id = randomHexString( gCountOfCallIdDigits ); + return id; +}; + +const gCountOfDirectPipeIdDigits: number = 10; +export const randomDirectPipeID = function(): string { + const id = randomHexString( gCountOfDirectPipeIdDigits ); + return id; +}; + +export const prepareAnswerJSON = function( joMessage: any ): any { + const joAnswer: any = { + id: joMessage?.id ? joMessage.id.toString() : randomCallID(), + method: joMessage?.method ? joMessage.method.toString() : "", + error: null + }; + return joAnswer; +}; + +export const makeValidSignalingServerURL = function( strSignalingServerURL?: string ): string { + const proto = settings.net.secure ? "wss" : "ws"; + return strSignalingServerURL + ? strSignalingServerURL.toString() + : proto + "://" + settings.net.hostname + ":" + settings.net.ports.signaling; +}; + +export const zeroPaddingLeft = function( val: any, cntCharsNeeded: number ): string { + if( val == null || val == undefined ) + return val; + let s = val.toString(); + while( s.length < cntCharsNeeded ) + s = "0" + s; + return s; +}; +export const zeroPaddingRight = function( val: any, cntCharsNeeded: number ): string { + if( val == null || val == undefined ) + return val; + let s = val.toString(); + while( s.length < cntCharsNeeded ) + s = s + "0"; + return s; +}; + +export const parseDateTime = function( ts?: any ): Date | null { + if( ts === null || ts === undefined ) + return ts; + if( typeof ts !== "string" ) + return null; + // example: + // 0----|----1----|----2----|---- + // 012345678901234567890123456789 + // "2020/03/19-19:42:55.663" + const year = parseInt( ts.substring( 0, 4 ), 10 ); + const month = parseInt( ts.substring( 5, 7 ), 10 ) + 1; + const day = parseInt( ts.substring( 8, 10 ), 10 ); + const hour = parseInt( ts.substring( 11, 13 ), 10 ); + const minute = parseInt( ts.substring( 14, 16 ), 10 ); + const second = parseInt( ts.substring( 17, 19 ), 10 ); + let millisecond: any = ts.substring( 20 ); + if( millisecond.length > 3 ) + millisecond = millisecond.substring( 0, 3 ); + else { + while( millisecond.length < 3 ) + millisecond = "0" + millisecond; + } + millisecond = parseInt( millisecond, 10 ); + const u = Date.UTC( year, month, day, hour, minute, second, millisecond ); + const d = new Date( u ); + d.setMilliseconds( millisecond ); + return d; +}; +export const formatDateTime = function( + dt: any, + isDate?: boolean, isTime?: boolean, isMilliseconds?: boolean, + sepDate?: string, sepTime?: string, sepBetween?: string, sepMilliseconds?: string +): string { + if( dt === null ) + return "null-date-time"; + if( dt === undefined ) + return "undefined-date-time"; + if( !( dt instanceof Date ) ) + return "not-a-date-time"; + isDate = ( isDate == null || isDate == undefined ) ? true : ( !!isDate ); + isTime = ( isTime == null || isTime == undefined ) ? true : ( !!isTime ); + if( ( !isDate ) && ( !isTime ) ) + return ""; + let s = ""; + if( isDate ) { + sepDate = ( sepDate == null || sepDate == undefined || ( typeof sepDate !== "string" ) ) + ? "/" + : sepDate; + const strDate = + zeroPaddingLeft( dt.getFullYear(), 4 ) + + sepDate + + zeroPaddingLeft( dt.getMonth() + 1, 2 ) + + sepDate + + zeroPaddingLeft( dt.getDate(), 2 ); + s += strDate; + } + if( isTime ) { + sepTime = ( sepTime == null || sepTime == undefined || ( typeof sepTime !== "string" ) ) + ? ":" + : sepTime; + if( isDate ) { + sepBetween = sepBetween ?? "-"; + s += sepBetween; + } + let strTime = + zeroPaddingLeft( dt.getHours(), 2 ) + + sepDate + + zeroPaddingLeft( dt.getMinutes(), 2 ) + + sepDate + + zeroPaddingLeft( dt.getSeconds(), 2 ); + isMilliseconds = ( isMilliseconds == null || isMilliseconds == undefined ) + ? true + : ( !!isMilliseconds ); + if( isMilliseconds ) { + sepMilliseconds = + ( sepMilliseconds == null || + sepMilliseconds == undefined || + ( typeof sepMilliseconds !== "string" ) ) + ? "." + : sepMilliseconds; + strTime += sepMilliseconds + zeroPaddingRight( dt.getMilliseconds(), 3 ); + } + s += strTime; + } + return s; +}; diff --git a/src/state.ts b/src/state.ts new file mode 100644 index 00000000..6cbde66d --- /dev/null +++ b/src/state.ts @@ -0,0 +1,532 @@ +import * as owaspUtils from "./owaspUtils.js"; +import * as imaTx from "./imaTx.js"; +import type * as discoveryTools from "./discoveryTools.js"; + +export interface TLoopStateSubPart { + isInProgress: boolean + wasInProgress: boolean +} + +export interface TLoopState { + oracle: TLoopStateSubPart + m2s: TLoopStateSubPart + s2m: TLoopStateSubPart + s2s: TLoopStateSubPart +} + +export interface TTokeInformation { + abi: object + address: string +} + +export const gDefaultValueForLoopState: TLoopState = { + oracle: { + isInProgress: false, + wasInProgress: false + }, + m2s: { + isInProgress: false, + wasInProgress: false + }, + s2m: { + isInProgress: false, + wasInProgress: false + }, + s2s: { + isInProgress: false, + wasInProgress: false + } +}; + +export interface TAccount { + address_?: string + privateKey: string | null + address: any + strTransactionManagerURL: string + nTmPriority: number + strSgxURL: string + strSgxKeyName: string + strPathSslKey: string + strPathSslCert: string + strBlsKeyName: string +} + +export interface TOneChainProperties { + joAccount: TAccount + transactionCustomizer: imaTx.TransactionCustomizer + ethersProvider: owaspUtils.ethersMod.ethers.providers.JsonRpcProvider | null + strURL: string + strChainName: string + chainId: string | number + strPathAbiJson: string + joAbiIMA: any + bHaveAbiIMA: boolean + joErc20: any | null + joErc721: any | null + joErc1155: any | null + strCoinNameErc20: string // in-JSON coin name + strCoinNameErc721: string // in-JSON coin name + strCoinNameErc1155: string // in-JSON coin name + strPathJsonErc20: string + strPathJsonErc721: string + strPathJsonErc1155: string +} + +export interface TPropertiesOfChains { + mn: TOneChainProperties + sc: TOneChainProperties + tc: TOneChainProperties +} + +function constructChainProperties(): TPropertiesOfChains { + return { + mn: { + joAccount: { + privateKey: + owaspUtils.toEthPrivateKey( process.env.PRIVATE_KEY_FOR_ETHEREUM ), + address: + function(): string { return owaspUtils.fnAddressImpl_( this ); }, + strTransactionManagerURL: + owaspUtils.toStringURL( + process.env.TRANSACTION_MANAGER_URL_ETHEREUM ), + nTmPriority: + owaspUtils.toInteger( + process.env.TRANSACTION_MANAGER_PRIORITY_ETHEREUM ) || 5, + strSgxURL: owaspUtils.toStringURL( process.env.SGX_URL_ETHEREUM ), + strSgxKeyName: owaspUtils.toStringURL( process.env.SGX_KEY_ETHEREUM ), + strPathSslKey: + ( process.env.SGX_SSL_KEY_FILE_ETHEREUM ?? "" ).toString().trim(), + strPathSslCert: + ( process.env.SGX_SSL_CERT_FILE_ETHEREUM ?? "" ).toString().trim(), + strBlsKeyName: owaspUtils.toStringURL( process.env.BLS_KEY_ETHEREUM ) + }, + transactionCustomizer: imaTx.getTransactionCustomizerForMainNet(), + ethersProvider: null, + strURL: owaspUtils.toStringURL( process.env.URL_W3_ETHEREUM ), + strChainName: + ( process.env.CHAIN_NAME_ETHEREUM ?? "Mainnet" ).toString().trim(), + chainId: owaspUtils.toInteger( process.env.CID_ETHEREUM ) ?? -4, + strPathAbiJson: "", + joAbiIMA: { }, + bHaveAbiIMA: false, + joErc20: null, + joErc721: null, + joErc1155: null, + strCoinNameErc20: "", // in-JSON coin name + strCoinNameErc721: "", // in-JSON coin name + strCoinNameErc1155: "", // in-JSON coin name + strPathJsonErc20: "", + strPathJsonErc721: "", + strPathJsonErc1155: "" + }, + sc: { + joAccount: { + privateKey: + owaspUtils.toEthPrivateKey( process.env.PRIVATE_KEY_FOR_SCHAIN ), + address: + function(): string { return owaspUtils.fnAddressImpl_( this ); }, + strTransactionManagerURL: + owaspUtils.toStringURL( process.env.TRANSACTION_MANAGER_URL_S_CHAIN ), + nTmPriority: + owaspUtils.toInteger( + process.env.TRANSACTION_MANAGER_PRIORITY_S_CHAIN ) || 5, + strSgxURL: owaspUtils.toStringURL( process.env.SGX_URL_S_CHAIN ), + strSgxKeyName: owaspUtils.toStringURL( process.env.SGX_KEY_S_CHAIN ), + strPathSslKey: + ( process.env.SGX_SSL_KEY_FILE_S_CHAIN ?? "" ).toString().trim(), + strPathSslCert: + ( process.env.SGX_SSL_CERT_FILE_S_CHAIN ?? "" ).toString().trim(), + strBlsKeyName: owaspUtils.toStringURL( process.env.BLS_KEY_S_CHAIN ) + }, + transactionCustomizer: imaTx.getTransactionCustomizerForSChain(), + ethersProvider: null, + strURL: owaspUtils.toStringURL( process.env.URL_W3_S_CHAIN ), + strChainName: + ( process.env.CHAIN_NAME_SCHAIN ?? "id-S-chain" ).toString().trim(), + chainId: owaspUtils.toInteger( process.env.CID_SCHAIN ) ?? -4, + strPathAbiJson: "", + joAbiIMA: { }, + bHaveAbiIMA: false, + joErc20: null, + joErc721: null, + joErc1155: null, + strCoinNameErc20: "", // in-JSON coin name + strCoinNameErc721: "", // in-JSON coin name + strCoinNameErc1155: "", // in-JSON coin name + strPathJsonErc20: "", + strPathJsonErc721: "", + strPathJsonErc1155: "" + }, + tc: { + joAccount: { + privateKey: + owaspUtils.toEthPrivateKey( process.env.PRIVATE_KEY_FOR_SCHAIN_TARGET ), + address: + function(): string { return owaspUtils.fnAddressImpl_( this ); }, + strTransactionManagerURL: + owaspUtils.toStringURL( + process.env.TRANSACTION_MANAGER_URL_S_CHAIN_TARGET ), + nTmPriority: + owaspUtils.toInteger( + process.env.TRANSACTION_MANAGER_PRIORITY_S_CHAIN_TARGET ) ?? 5, + strSgxURL: owaspUtils.toStringURL( process.env.SGX_URL_S_CHAIN_TARGET ), + strSgxKeyName: owaspUtils.toStringURL( process.env.SGX_KEY_S_CHAIN_TARGET ), + strPathSslKey: + ( process.env.SGX_SSL_KEY_FILE_S_CHAIN_TARGET ?? "" ).toString().trim(), + strPathSslCert: + ( process.env.SGX_SSL_CERT_FILE_S_CHAIN_TARGET ?? "" ).toString().trim(), + strBlsKeyName: owaspUtils.toStringURL( process.env.BLS_KEY_T_CHAIN ) + }, + transactionCustomizer: imaTx.getTransactionCustomizerForSChainTarget(), + ethersProvider: null, + strURL: owaspUtils.toStringURL( process.env.URL_W3_S_CHAIN_TARGET ), + strChainName: + ( process.env.CHAIN_NAME_SCHAIN_TARGET ?? "id-T-chain" ).toString().trim(), + chainId: owaspUtils.toInteger( process.env.CID_SCHAIN_TARGET ) || -4, + strPathAbiJson: "", + joAbiIMA: { }, + bHaveAbiIMA: false, + joErc20: null, + joErc721: null, + joErc1155: null, + strCoinNameErc20: "", // in-JSON coin name + strCoinNameErc721: "", // in-JSON coin name + strCoinNameErc1155: "", // in-JSON coin name + strPathJsonErc20: "", + strPathJsonErc721: "", + strPathJsonErc1155: "" + } + }; +} + +export interface TIMAAction { + name: string + fn: () => Promise < boolean > +} + +export interface TIMAState { + loopState: TLoopState + + strLogFilePath: string + nLogMaxSizeBeforeRotation: number + nLogMaxFilesCount: number + isPrintGathered: boolean + isPrintSecurityValues: boolean + isPrintPWA: boolean + isDynamicLogInDoTransfer: boolean + isDynamicLogInBlsSigner: boolean + + bIsNeededCommonInit: boolean + // use BLS message signing, turned on with --sign-messages + bSignMessages: boolean + // scanned S-Chain network description + joSChainNetworkInfo: discoveryTools.TSChainNetworkInfo | null + // path to bls_glue app, must have if --sign-messages specified + strPathBlsGlue: string + // path to hash_g1 app, must have if --sign-messages specified + strPathHashG1: string + // path to verify_bls app, optional, + // if specified then we will verify gathered BLS signature + strPathBlsVerify: string + + // true - just show configuration values and exit + bShowConfigMode: boolean + + isEnabledMultiCall: boolean + + bNoWaitSChainStarted: boolean + nMaxWaitSChainAttempts: number // 20 + + nAmountOfWei: any + nAmountOfToken: any + arrAmountsOfTokens: any[] | null + idToken: any + idTokens: any[] | null + + nTransferBlockSizeM2S: number + nTransferBlockSizeS2M: number + nTransferBlockSizeS2S: number + nTransferStepsM2S: number + nTransferStepsS2M: number + nTransferStepsS2S: number + nMaxTransactionsM2S: number + nMaxTransactionsS2M: number + nMaxTransactionsS2S: number + + nBlockAwaitDepthM2S: number + nBlockAwaitDepthS2M: number + nBlockAwaitDepthS2S: number + nBlockAgeM2S: number + nBlockAgeS2M: number + nBlockAgeS2S: number + + nLoopPeriodSeconds: number + + nNodeNumber: number // S-Chain node number(zero based) + nNodesCount: number + nTimeFrameSeconds: number // 0-disable, 60-recommended + nNextFrameGap: number + + nAutoExitAfterSeconds: number // 0-disable + + joCommunityPool: owaspUtils.ethersMod.Contract | null // only main net + joDepositBoxETH: owaspUtils.ethersMod.Contract | null // only main net + joDepositBoxERC20: owaspUtils.ethersMod.Contract | null // only main net + joDepositBoxERC721: owaspUtils.ethersMod.Contract | null // only main net + joDepositBoxERC1155: owaspUtils.ethersMod.Contract | null // only main net + joDepositBoxERC721WithMetadata: owaspUtils.ethersMod.Contract | null // only main net + joLinker: owaspUtils.ethersMod.Contract | null // only main net + + isWithMetadata721: boolean + + joTokenManagerETH: owaspUtils.ethersMod.Contract | null // only s-chain + joTokenManagerETHTarget: owaspUtils.ethersMod.Contract | null + joTokenManagerERC20: owaspUtils.ethersMod.Contract | null // only s-chain + joTokenManagerERC20Target: owaspUtils.ethersMod.Contract | null // only s-chain + joTokenManagerERC721: owaspUtils.ethersMod.Contract | null // only sc target + joTokenManagerERC721Target: owaspUtils.ethersMod.Contract | null // only sc target + joTokenManagerERC1155: owaspUtils.ethersMod.Contract | null // only s-chain + joTokenManagerERC1155Target: owaspUtils.ethersMod.Contract | null // only sc target + joTokenManagerERC721WithMetadata: owaspUtils.ethersMod.Contract | null // only sc target + joTokenManagerERC721WithMetadataTarget: owaspUtils.ethersMod.Contract | null // only sc target + joCommunityLocker: owaspUtils.ethersMod.Contract | null // only s-chain + joCommunityLockerTarget: owaspUtils.ethersMod.Contract | null // only sc target + joMessageProxyMainNet: owaspUtils.ethersMod.Contract | null + joMessageProxySChain: owaspUtils.ethersMod.Contract | null + joMessageProxySChainTarget: owaspUtils.ethersMod.Contract | null // only sc target + joTokenManagerLinker: owaspUtils.ethersMod.Contract | null + joTokenManagerLinkerTarget: owaspUtils.ethersMod.Contract | null // only sc target + joEthErc20: owaspUtils.ethersMod.Contract | null // only s-chain + joEthErc20Target: owaspUtils.ethersMod.Contract | null // only sc target + + joConstantsHolder?: owaspUtils.ethersMod.Contract | null + joNodes?: owaspUtils.ethersMod.Contract | null + joKeyStorage?: owaspUtils.ethersMod.Contract | null + joSChains?: owaspUtils.ethersMod.Contract | null + joSChainsInternal?: owaspUtils.ethersMod.Contract | null + joSkaleDKG?: owaspUtils.ethersMod.Contract | null + joSkaleManager?: owaspUtils.ethersMod.Contract | null + joSkaleToken?: owaspUtils.ethersMod.Contract | null + joValidatorService?: owaspUtils.ethersMod.Contract | null + joWallets?: owaspUtils.ethersMod.Contract | null + + chainProperties: TPropertiesOfChains + + strPathAbiJsonSkaleManager: string + joAbiSkaleManager: any + bHaveSkaleManagerABI: boolean + + strChainNameOriginChain: string + + strAddrErc20Explicit: string + strAddrErc20ExplicitTarget: string // S<->S target + strAddrErc721Explicit: string + strAddrErc721ExplicitTarget: string // S<->S target + strAddrErc1155Explicit: string + strAddrErc1155ExplicitTarget: string // S<->S target + + isPWA: boolean + nTimeoutSecondsPWA: number + + nMonitoringPort: number // 0 - default, means monitoring server is disabled + bLogMonitoringServer: boolean + + strReimbursementChain: string + isShowReimbursementBalance: boolean + nReimbursementRecharge: string | number | null + nReimbursementWithdraw: string | number | null + nReimbursementRange: number // < 0 - do not change anything + isReimbursementEstimate?: boolean + + joSChainDiscovery: { + isSilentReDiscovery: boolean + // zero to disable (for debugging only) + repeatIntervalMilliseconds: number + periodicDiscoveryInterval: number + } + + // S-Chain to S-Chain transfer options + optsS2S: { + // is S-Chain to S-Chain transfers enabled + isEnabled: boolean + strNetworkBrowserPath: string | null + } + + nJsonRpcPort: number // 0 to disable + isCrossImaBlsMode: boolean + + arrActions: TIMAAction[] // array of actions to run + + receiver?: any | null + + haveOneTokenIdentifier: boolean + haveArrayOfTokenIdentifiers: boolean +}; + +let imaState: TIMAState | null = null; + +export function get(): TIMAState { + if( imaState ) + return imaState; + imaState = { + loopState: gDefaultValueForLoopState, + + strLogFilePath: "", + nLogMaxSizeBeforeRotation: -1, + nLogMaxFilesCount: -1, + isPrintGathered: true, + isPrintSecurityValues: false, + isPrintPWA: false, + isDynamicLogInDoTransfer: true, + isDynamicLogInBlsSigner: false, + + bIsNeededCommonInit: true, + // use BLS message signing, turned on with --sign-messages + bSignMessages: false, + // scanned S-Chain network description + joSChainNetworkInfo: null, + // path to bls_glue app, must have if --sign-messages specified + strPathBlsGlue: "", + // path to hash_g1 app, must have if --sign-messages specified + strPathHashG1: "", + // path to verify_bls app, optional, + // if specified then we will verify gathered BLS signature + strPathBlsVerify: "", + + // true - just show configuration values and exit + bShowConfigMode: false, + + isEnabledMultiCall: true, + + bNoWaitSChainStarted: false, + nMaxWaitSChainAttempts: Number.MAX_SAFE_INTEGER, // 20 + + nAmountOfWei: 0, + nAmountOfToken: 0, + arrAmountsOfTokens: null, + idToken: 0, + idTokens: null, + + nTransferBlockSizeM2S: 4, + nTransferBlockSizeS2M: 4, + nTransferBlockSizeS2S: 4, + nTransferStepsM2S: 8, + nTransferStepsS2M: 8, + nTransferStepsS2S: 8, + nMaxTransactionsM2S: 0, + nMaxTransactionsS2M: 0, + nMaxTransactionsS2S: 0, + + nBlockAwaitDepthM2S: 0, + nBlockAwaitDepthS2M: 0, + nBlockAwaitDepthS2S: 0, + nBlockAgeM2S: 0, + nBlockAgeS2M: 0, + nBlockAgeS2S: 0, + + nLoopPeriodSeconds: 10, + + nNodeNumber: 0, // S-Chain node number(zero based) + nNodesCount: 1, + nTimeFrameSeconds: 0, // 0-disable, 60-recommended + nNextFrameGap: 10, + + nAutoExitAfterSeconds: 0, // 0-disable + + joCommunityPool: null, // only main net + joDepositBoxETH: null, // only main net + joDepositBoxERC20: null, // only main net + joDepositBoxERC721: null, // only main net + joDepositBoxERC1155: null, // only main net + joDepositBoxERC721WithMetadata: null, // only main net + joLinker: null, // only main net + + isWithMetadata721: false, + + joTokenManagerETH: null, // only s-chain + joTokenManagerETHTarget: null, + joTokenManagerERC20: null, // only s-chain + joTokenManagerERC20Target: null, // only s-chain + joTokenManagerERC721: null, // only sc target + joTokenManagerERC721Target: null, // only sc target + joTokenManagerERC1155: null, // only s-chain + joTokenManagerERC1155Target: null, // only sc target + joTokenManagerERC721WithMetadata: null, // only sc target + joTokenManagerERC721WithMetadataTarget: null, // only sc target + joCommunityLocker: null, // only s-chain + joCommunityLockerTarget: null, // only sc target + joMessageProxyMainNet: null, + joMessageProxySChain: null, + joMessageProxySChainTarget: null, // only sc target + joTokenManagerLinker: null, + joTokenManagerLinkerTarget: null, // only sc target + joEthErc20: null, // only s-chain + joEthErc20Target: null, // only sc target + + chainProperties: constructChainProperties(), + + strPathAbiJsonSkaleManager: "", + joAbiSkaleManager: { }, + bHaveSkaleManagerABI: false, + + strChainNameOriginChain: + ( process.env.CHAIN_NAME_SCHAIN_ORIGIN ?? "Mainnet" ).toString().trim(), + + strAddrErc20Explicit: "", + strAddrErc20ExplicitTarget: "", // S<->S target + strAddrErc721Explicit: "", + strAddrErc721ExplicitTarget: "", // S<->S target + strAddrErc1155Explicit: "", + strAddrErc1155ExplicitTarget: "", // S<->S target + + isPWA: true, + nTimeoutSecondsPWA: 60, + + nMonitoringPort: 0, // 0 - default, means monitoring server is disabled + bLogMonitoringServer: false, + + strReimbursementChain: "", + isShowReimbursementBalance: false, + nReimbursementRecharge: 0, + nReimbursementWithdraw: 0, + nReimbursementRange: -1, // < 0 - do not change anything + + joSChainDiscovery: { + isSilentReDiscovery: false, + // zero to disable (for debugging only) + repeatIntervalMilliseconds: 5 * 1000, + periodicDiscoveryInterval: 5 * 60 * 1000 + }, + + // S-Chain to S-Chain transfer options + optsS2S: { + // is S-Chain to S-Chain transfers enabled + isEnabled: true, + strNetworkBrowserPath: null + }, + + nJsonRpcPort: 0, // 0 to disable + isCrossImaBlsMode: false, + + arrActions: [], // array of actions to run + + haveOneTokenIdentifier: false, + haveArrayOfTokenIdentifiers: false + }; + return imaState; +} + +export function set( imaStateNew: TIMAState ): TIMAState { + imaState = imaStateNew; + return imaState; +} + +let gFlagIsPreventExitAfterLastAction = false; + +export function isPreventExitAfterLastAction(): boolean { + return gFlagIsPreventExitAfterLastAction; +} + +export function setPreventExitAfterLastAction( isPrevent: any ): void { + gFlagIsPreventExitAfterLastAction = ( !!isPrevent ); +} diff --git a/src/threadInfo.ts b/src/threadInfo.ts new file mode 100644 index 00000000..71e54a00 --- /dev/null +++ b/src/threadInfo.ts @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * SKALE IMA is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SKALE IMA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with SKALE IMA. If not, see . + */ + +/** + * @file loop.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as worker_threads from "worker_threads"; +import * as log from "./log.js"; + +const Worker = worker_threads.Worker; +export { Worker }; + +const joCustomThreadProperties: any = { }; +export { joCustomThreadProperties }; + +export const sleep = async( milliseconds: number ): Promise => { + await new Promise( resolve => setTimeout( resolve, milliseconds ) ); +}; + +export function getCurrentThreadID(): number { + return worker_threads.threadId; +} + +export function isMainThread(): boolean { + return ( !!( worker_threads.isMainThread ) ); +} + +export function threadDescription( isColorized?: boolean ): string { + if( typeof isColorized === "undefined" ) + isColorized = true; + const tid: number = getCurrentThreadID(); + const st: string = isMainThread() ? "main" : "worker"; + return isColorized + ? ( log.fmtAttention( st ) + log.fmtDebug( " thread " ) + + log.fmtInformation( tid ) ) + : ( st + " thread " + tid ); +} diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 00000000..1c4af757 --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + "target": "es2020", + "module": "es2020", + "declaration": true /* Generates corresponding '.d.ts' file. */, + "outDir": "build" /* Redirect output structure to the directory. */, + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + "resolveJsonModule": true, + /* Module Resolution Options */ + "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + /* Advanced Options */ + // "skipLibCheck": true /* Skip type checking of declaration files. */, + // "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ + + "paths": { + "*": [ + "./*.ts" + ] + } + }, + "exclude": ["node_modules", "dist"], + "include": [ + "./*.ts" + ] +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..e68bfdd7 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file utils.ts + * @copyright SKALE Labs 2019-Present + */ + +import * as log from "./log.js"; +import * as owaspUtils from "./owaspUtils.js"; +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; +import * as threadInfo from "./threadInfo.js"; +import type * as state from "./state.js"; +import type * as discoveryTools from "./discoveryTools.js"; + +import { v4 as uuid } from "uuid"; +export { uuid }; + +const ethersMod = owaspUtils.ethersMod; +export { ethersMod }; + +export interface TTokesABIHolder { + joABI: object +} + +export function replaceAll( str: string, find: string, replace: string ): string { + return str.replace( new RegExp( find, "g" ), replace ); +} + +export function normalizePath( strPath: string ): string { + strPath = strPath.replace( /^~/, os.homedir() ); + strPath = path.normalize( strPath ); + strPath = path.resolve( strPath ); + return strPath; +} + +export function getRandomFileName(): string { + const timestamp = new Date().toISOString().replace( /[-:.]/g, "" ); + const random = Math.random().toString().substring( 2, 8 ); + const randomNumber = timestamp + random; + return randomNumber; +} + +export function fileExists( strPath: string ): boolean { + try { + if( fs.existsSync( strPath ) ) { + const stats = fs.statSync( strPath ); + if( stats.isFile() ) + return true; + } + } catch ( err ) {} + return false; +} + +export function fileLoad( strPath: string, strDefault?: string | null ): string { + strDefault = strDefault ?? ""; + if( !fileExists( strPath ) ) + return strDefault; + try { + const s = fs.readFileSync( strPath ).toString(); + return s; + } catch ( err ) {} + return strDefault; +} + +export function fileSave( strPath: string, s: string ): boolean { + try { + fs.writeFileSync( strPath, s ); + return true; + } catch ( err ) {} + return false; +} + +export function jsonFileLoad( strPath: string, joDefault?: any, bLogOutput?: boolean ): any { + if( bLogOutput == undefined || bLogOutput == null ) + bLogOutput = false; + joDefault = joDefault || {}; + if( bLogOutput ) + log.debug( "Will load JSON file {}...", strPath ); + + if( !fileExists( strPath ) ) { + if( bLogOutput ) + log.error( "Cannot load JSON file {}, it does not exist", strPath ); + return joDefault; + } + try { + const s = fs.readFileSync( strPath ).toString(); + if( bLogOutput ) + log.debug( "Did loaded content of JSON file {}, will parse it...", strPath ); + + const jo: any = JSON.parse( s ); + if( bLogOutput ) + log.success( "Done, loaded content of JSON file {}.", strPath ); + return jo; + } catch ( err ) { + log.error( "Failed to load JSON file {}, error is: {err}, stack is:\n{stack}", + strPath, err, err ); + } + return joDefault; +} + +export function jsonFileSave( strPath: string, jo?: any, bLogOutput?: boolean ): any { + if( bLogOutput == undefined || bLogOutput == null ) + bLogOutput = false; + if( bLogOutput ) + log.debug( "Will save JSON file {}...", strPath ); + try { + const s = JSON.stringify( jo, null, 4 ); + fs.writeFileSync( strPath, s ); + if( bLogOutput ) + log.success( "Done, saved content of JSON file {}.", strPath ); + return true; + } catch ( err ) { + log.error( " failed to save JSON file {}, error is: {err}, stack is:\n{stack}", + strPath, err, err ); + } + return false; +} + +const gMillisecondsToSleepStepWaitForClonedTokenToAppear: number = 1000; + +export async function waitForClonedTokenToAppear( + sc: any, + strTokenSuffix: string, // example "erc20" + addressCallFrom: string, + cntAttempts: number, + tokensMN: TTokesABIHolder, + strMainnetName: string +): Promise { + const strTokenSuffixLC = strTokenSuffix.toLowerCase(); + const strTokenSuffixUC = + owaspUtils.replaceAll( strTokenSuffix.toUpperCase(), "_WITH_METADATA", "_with_metadata" ); + const strTokenSuffixLCshort = owaspUtils.replaceAll( strTokenSuffixLC, "_with_metadata", "" ); + const ts0 = log.timestampHR(); + let ts1; + log.information( "Waiting for {} token to appear automatically deployed on S-Chain {}...", + strTokenSuffixUC, sc.chainName ); + log.debug( "... source chain name is {}", strMainnetName ); + log.debug( "... destination {} address is {}", "TokenManager" + strTokenSuffixUC, + sc.joABI["token_manager_" + strTokenSuffixLC + "_address"] ); + const contractTokenManager = new owaspUtils.ethersMod.ethers.Contract( + sc.joABI["token_manager_" + strTokenSuffixLC + "_address"], + sc.joABI["token_manager_" + strTokenSuffixLC + "_abi"], + sc.ethersProvider ); + for( let idxAttempt = 0; idxAttempt < cntAttempts; ++idxAttempt ) { + log.information( "Discovering {} step {}...", strTokenSuffixUC, idxAttempt ); + if( gMillisecondsToSleepStepWaitForClonedTokenToAppear > 0 ) + await threadInfo.sleep( gMillisecondsToSleepStepWaitForClonedTokenToAppear ); + const addressOnSChain = + await contractTokenManager.callStatic[ + "clones" + log.capitalizeFirstLetter( strTokenSuffixLCshort )]( + sc.ethersMod.ethers.utils.id( strMainnetName ), + ( tokensMN.joABI as any )[strTokenSuffixUC + "_address"], + { from: addressCallFrom } + ); + if( addressOnSChain != "0x0000000000000000000000000000000000000000" ) { + ts1 = log.timestampHR(); + log.success( "Done, duration is {}", log.getDurationString( ts0, ts1 ) ); + log.success( "Discovered {} instantiated on S-Chain {} at address {}", + strTokenSuffixUC, sc.chainName, addressOnSChain ); + return addressOnSChain; + } + } + ts1 = log.timestampHR(); + log.error( "Failed to discover {} instantiated on S-Chain {}", strTokenSuffixUC, sc.chainName ); + throw new Error( `Failed to discover ${strTokenSuffixUC} instantiated ` + + `on S-Chain ${sc.chainName}` ); +} + +export async function waitForClonedTokenAppearErc20( + sc: any, tokenERC20SC: state.TTokeInformation, joAccountSC: state.TAccount, + tokensMN: TTokesABIHolder, strMainnetName: string +): Promise { + if( "abi" in tokenERC20SC && typeof tokenERC20SC.abi === "object" && + "address" in tokenERC20SC && typeof tokenERC20SC.address === "string" + ) { + log.warning( "Skipping automatic ERC20 instantiation discovery, already done before" ); + return; + } + const addressCallFrom = joAccountSC.address(); + const addressOnSChain = await waitForClonedTokenToAppear( + sc, "erc20", addressCallFrom, 40, tokensMN, strMainnetName ); + tokenERC20SC.abi = JSON.parse( JSON.stringify( ( tokensMN.joABI as any ).ERC20_abi ) ); + tokenERC20SC.address = addressOnSChain ? addressOnSChain.toString() : ""; +} + +export async function waitForClonedTokenAppearErc721( + sc: any, tokenERC721SC: state.TTokeInformation, joAccountSC: state.TAccount, + tokensMN: TTokesABIHolder, strMainnetName: string +): Promise { + if( "abi" in tokenERC721SC && typeof tokenERC721SC.abi === "object" && + "address" in tokenERC721SC && typeof tokenERC721SC.address === "string" + ) { + log.warning( "Skipping automatic ERC721instantiation discovery, already done before" ); + return; + } + const addressCallFrom = joAccountSC.address(); + const addressOnSChain = + await waitForClonedTokenToAppear( + sc, "erc721", addressCallFrom, 40, tokensMN, strMainnetName ); + tokenERC721SC.abi = JSON.parse( JSON.stringify( ( tokensMN.joABI as any ).ERC721_abi ) ); + tokenERC721SC.address = addressOnSChain ? addressOnSChain.toString() : ""; +} + +export async function waitForClonedTokenAppearErc721WithMetadata( + sc: any, tokenERC721SC: state.TTokeInformation, joAccountSC: state.TAccount, + tokensMN: TTokesABIHolder, strMainnetName: string +): Promise { + if( "abi" in tokenERC721SC && typeof tokenERC721SC.abi === "object" && + "address" in tokenERC721SC && typeof tokenERC721SC.address === "string" + ) { + log.warning( "Skipping automatic ERC721_with_metadata instantiation discovery, " + + "already done before" ); + return; + } + const addressCallFrom = joAccountSC.address(); + const addressOnSChain = await waitForClonedTokenToAppear( + sc, "erc721_with_metadata", addressCallFrom, 40, tokensMN, strMainnetName ); + tokenERC721SC.abi = + JSON.parse( JSON.stringify( ( tokensMN.joABI as any ).ERC721_with_metadata_abi ) ); + tokenERC721SC.address = addressOnSChain ? addressOnSChain.toString() : ""; +} + +export async function waitForClonedTokenAppearErc1155( + sc: any, tokenERC1155SC: state.TTokeInformation, joAccountSC: state.TAccount, + tokensMN: TTokesABIHolder, strMainnetName: string +): Promise { + if( "abi" in tokenERC1155SC && typeof tokenERC1155SC.abi === "object" && + "address" in tokenERC1155SC && typeof tokenERC1155SC.address === "string" + ) { + log.warning( "Skipping automatic ERC1155 instantiation discovery, already done before" ); + return; + } + const addressCallFrom = joAccountSC.address(); + const addressOnSChain = await waitForClonedTokenToAppear( + sc, "erc1155", addressCallFrom, 40, tokensMN, strMainnetName ); + tokenERC1155SC.abi = JSON.parse( JSON.stringify( ( tokensMN.joABI as any ).ERC1155_abi ) ); + tokenERC1155SC.address = addressOnSChain ? addressOnSChain.toString() : ""; +} + +export function hexToBytes( + strHex?: any, isInversiveOrder?: boolean +): Uint8Array { // convert a hex string to a byte array + isInversiveOrder = !!( + ( isInversiveOrder != null && isInversiveOrder != undefined && isInversiveOrder ) + ); + strHex = strHex ?? ""; + strHex = strHex.toString(); + strHex = strHex.trim().toLowerCase(); + if( strHex.length > 1 && strHex[0] == "0" && ( strHex[1] == "x" || strHex[1] == "X" ) ) + strHex = strHex.substr( 2, strHex.length - 2 ); + if( ( strHex.length & 1 ) !== 0 ) + strHex = "0" + strHex; + const cnt = strHex.length; + let i: number, j: number; + const arrBytes = new Uint8Array( cnt / 2 ); + for( i = 0, j = 0; i < cnt; ++j, i += 2 ) + arrBytes[j] = parseInt( strHex.substr( i, 2 ), 16 ); + if( isInversiveOrder ) + return arrBytes.reverse(); + return arrBytes; +} + +export function bytesToHex( + arrBytes: Uint8Array, isInversiveOrder?: boolean +): string { // convert a byte array to a hex string + isInversiveOrder = !!( + ( isInversiveOrder != null && isInversiveOrder != undefined && isInversiveOrder ) + ); + const hex: any[] = []; + for( let i = 0; i < arrBytes.length; i++ ) { + const current = arrBytes[i] < 0 ? arrBytes[i] + 256 : arrBytes[i]; + const c0 = ( current >>> 4 ).toString( 16 ); + const c1 = ( current & 0xF ).toString( 16 ); + if( isInversiveOrder ) { + hex.splice( 0, 0, c0 ); + hex.splice( 1, 0, c1 ); + } else { + hex.push( c0 ); + hex.push( c1 ); + } + } + return hex.join( "" ); +} + +export function bytesAlignLeftWithZeroes( arrBytes: Uint8Array, cntMin: number ): Uint8Array { + if( arrBytes.length >= cntMin ) + return arrBytes; + const cntNewZeros = cntMin - arrBytes.length; + // By default Uint8Array, Uint16Array and Uint32Array classes keep zeros as it's values. + const arrNewZeros = new Uint8Array( cntNewZeros ); + arrBytes = bytesConcat( arrNewZeros, arrBytes ); + return arrBytes; +} + +export function bytesAlignRightWithZeroes( arrBytes: Uint8Array, cntMin: number ): Uint8Array { + if( arrBytes.length >= cntMin ) + return arrBytes; + const cntNewZeros = cntMin - arrBytes.length; + // By default Uint8Array, Uint16Array and Uint32Array classes keep zeros as it's values. + const arrNewZeros = new Uint8Array( cntNewZeros ); + arrBytes = bytesConcat( arrBytes, arrNewZeros ); + return arrBytes; +} + +export function concatUint8Arrays( + a: Uint8Array, b: Uint8Array ): Uint8Array { // a, b TypedArray of same type + if( typeof a === "string" ) + a = hexToBytes( a ); + if( typeof b === "string" ) + b = hexToBytes( b ); + const c = new Uint8Array( a.length + b.length ); + c.set( a, 0 ); + c.set( b, a.length ); + return c; +} + +export function concatByte( ui8a: Uint8Array, byte: number ): Uint8Array { + const b = new Uint8Array( 1 ); + b[0] = byte; + return concatUint8Arrays( ui8a, b ); +} + +export function bytesConcat( a1?: Uint8Array, a2?: Uint8Array ): Uint8Array { + a1 = a1 ?? new Uint8Array(); + a2 = a2 ?? new Uint8Array(); + return concatUint8Arrays( a1, a2 ); +} + +export function toBuffer( ab?: any ): Buffer { + return Buffer.from( new Uint8Array( ab ) ); +} + +export function discoverCoinNameInJSON( jo?: any ): string { + if( typeof jo !== "object" ) + return ""; + const arrKeys = Object.keys( jo ); + let s1 = ""; + let s2 = ""; + let i; const cnt = arrKeys.length; + let j; + for( i = 0; i < cnt; ++i ) { + if( s1.length > 0 && s2.length > 0 ) + break; + const k = arrKeys[i]; + j = k.indexOf( "_address" ); + if( j > 0 ) { + s1 = k.substring( 0, j ); + continue; + } + j = k.indexOf( "_abi" ); + if( j > 0 ) { + s2 = k.substring( 0, j ); + continue; + } + } + if( s1.length === 0 || s2.length === 0 ) + return ""; + if( s1 !== s2 ) + return ""; + return s1; +} + +export function checkKeyExistInABI( + strName: string, strFile: string, joABI: any, strKey: string, isExitOnError?: boolean +): boolean { + if( isExitOnError == null || isExitOnError == undefined ) + isExitOnError = true; + try { + if( strKey in joABI ) + return true; + } catch ( err ) { + if( isExitOnError ) { + log.fatal( + "Loaded {} ABI JSON file {} does not contain needed key {}, stack is:\n{stack}", + strName, strFile, strKey, err ); + process.exit( 126 ); + } + } + return false; +} + +export function checkKeysExistInABI( + strName: string, strFile: string, joABI: any, arrKeys: any[], isExitOnError?: boolean +): boolean { + const cnt = arrKeys.length; + for( let i = 0; i < cnt; ++i ) { + const strKey = arrKeys[i]; + if( !checkKeyExistInABI( strName, strFile, joABI, strKey, isExitOnError ) ) + return false; + } + return true; +} + +export function composeSChainNodeUrl( joNode: discoveryTools.TSChainNode ): string { + if( "ip" in joNode && joNode?.ip && joNode.ip.length > 0 ) { + if( "httpRpcPort" in joNode && joNode?.httpRpcPort && joNode.httpRpcPort > 0 ) + return "http://" + joNode.ip + ":" + joNode.httpRpcPort; + if( "wsRpcPort" in joNode && joNode?.wsRpcPort && joNode.wsRpcPort > 0 ) + return "ws://" + joNode.ip + ":" + joNode.wsRpcPort; + if( "httpsRpcPort" in joNode && joNode?.httpsRpcPort && joNode.httpsRpcPort > 0 ) + return "https://" + joNode.ip + ":" + joNode.httpsRpcPort; + if( "wssRpcPort" in joNode && joNode?.wssRpcPort && joNode.wssRpcPort > 0 ) + return "wss://" + joNode.ip + ":" + joNode.wssRpcPort; + } + if( "ip6" in joNode && joNode?.ip6 && joNode.ip6.length > 0 ) { + if( "httpRpcPort6" in joNode && joNode?.httpRpcPort6 && joNode.httpRpcPort6 > 0 ) + return "http://[" + joNode.ip6 + "]:" + joNode.httpRpcPort6; + if( "wsRpcPort6" in joNode && joNode?.wsRpcPort6 && joNode.wsRpcPort6 > 0 ) + return "ws://[" + joNode.ip6 + "]:" + joNode.wsRpcPort6; + if( "httpsRpcPort6" in joNode && joNode?.httpsRpcPort6 && joNode.httpsRpcPort6 > 0 ) + return "https://[" + joNode.ip6 + "]:" + joNode.httpsRpcPort6; + if( "wssRpcPort6" in joNode && joNode?.wssRpcPort6 && joNode.wssRpcPort6 > 0 ) + return "wss://[" + joNode.ip6 + "]:" + joNode.wssRpcPort6; + } + return ""; +} + +export function composeImaAgentNodeUrl( + joNode: discoveryTools.TSChainNode, isThisNode: boolean ): string { + let nPort = -1; + if( "imaAgentRpcPort" in joNode && joNode?.imaAgentRpcPort && joNode.imaAgentRpcPort > 0 ) + nPort = joNode.imaAgentRpcPort; + // PROPOSAL = 0 + // CATCHUP = 1 + // WS_JSON = 2 + // HTTP_JSON = 3 + // BINARY_CONSENSUS = 4 + // ZMQ_BROADCAST = 5 + // IMA_MONITORING = 6 + // WSS_JSON = 7 + // HTTPS_JSON = 8 + // INFO_HTTP_JSON = 9 + // IMA_AGENT_JSON = 10 + if( nPort < 0 && joNode?.httpRpcPort && joNode.httpRpcPort > 0 ) + nPort = joNode.httpRpcPort - 3 + 10; + if( nPort < 0 && joNode?.wsRpcPort && joNode.wsRpcPort > 0 ) + nPort = joNode.wsRpcPort - 2 + 10; + if( nPort < 0 && joNode?.httpsRpcPort && joNode.httpsRpcPort > 0 ) + nPort = joNode.httpsRpcPort - 8 + 10; + if( nPort < 0 && joNode?.wssRpcPort && joNode.wssRpcPort > 0 ) + nPort = joNode.wssRpcPort - 7 + 10; + if( nPort > 0 ) { + const strNodeIP = isThisNode ? "127.0.0.1" : joNode.ip; + return "http://" + strNodeIP + ":" + nPort; + } + return ""; +} diff --git a/src/yarn.lock b/src/yarn.lock new file mode 100644 index 00000000..7598c839 --- /dev/null +++ b/src/yarn.lock @@ -0,0 +1,3414 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.0", "@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.3.tgz#797470a75fe0fbd5a53350ee715e85e87baff22d" + integrity sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.53.0": + version "8.53.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d" + integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w== + +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + +"@ethersproject/contracts@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + +"@ethersproject/experimental@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/experimental/-/experimental-5.7.0.tgz#9759639434d37beaedfd8acab6f3af7db246b92d" + integrity sha512-DWvhuw7Dg8JPyhMbh/CNYOwsTLjXRx/HGkacIL5rBocG8jJC0kmixwoK/J3YblO4vtcyBLMa+sV74RJZK2iyHg== + dependencies: + "@ethersproject/web" "^5.7.0" + ethers "^5.7.0" + scrypt-js "3.0.1" + +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.0.10": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/solidity@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + +"@ethersproject/units@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/wallet@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@fastify/busboy@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" + integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ== + +"@humanwhocodes/config-array@^0.11.13": + version "0.11.13" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" + integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== + dependencies: + "@humanwhocodes/object-schema" "^2.0.1" + debug "^4.1.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" + integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== + +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@types/bn.js@^5.1.0": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.2.tgz#162f5238c46f4bcbac07a98561724eca1fcf0c5e" + integrity sha512-dkpZu0szUtn9UXTmw+e0AJFd4D2XAxDnsCLdc05SfqpqzPEBft8eQr8uaFitfo/dUUOZERaLec2hHMG87A4Dxg== + dependencies: + "@types/node" "*" + +"@types/body-parser@*", "@types/body-parser@^1.19.5": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/connect@^3.4.33": + version "3.4.36" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.36.tgz#e511558c15a39cb29bd5357eebb57bd1459cd1ab" + integrity sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.33": + version "4.17.41" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz#5077defa630c2e8d28aa9ffc2c01c157c305bef6" + integrity sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.21": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/glob@~7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/ioredis@^4.28.2": + version "4.28.10" + resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.28.10.tgz#40ceb157a4141088d1394bb87c98ed09a75a06ff" + integrity sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ== + dependencies: + "@types/node" "*" + +"@types/json-schema@^7.0.12": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/mime@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" + integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/minimatch@*": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== + +"@types/node@*": + version "20.8.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.4.tgz#0e9ebb2ff29d5c3302fc84477d066fa7c6b441aa" + integrity sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A== + dependencies: + undici-types "~5.25.1" + +"@types/node@^12.12.54": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + +"@types/pbkdf2@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" + integrity sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ== + dependencies: + "@types/node" "*" + +"@types/qs@*": + version "6.9.10" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.10.tgz#0af26845b5067e1c9a622658a51f60a3934d51e8" + integrity sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/secp256k1@^4.0.1": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.4.tgz#33c760de627fce1f449c2d4270da07e4da54c830" + integrity sha512-oN0PFsYxDZnX/qSJ5S5OwaEDTYfekhvaM5vqui2bu1AA39pKofmgL104Q29KiOXizXS2yLjSzc5YdTyMKdcy4A== + dependencies: + "@types/node" "*" + +"@types/semver@^7.5.0": + version "7.5.6" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" + integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.5" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033" + integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ== + dependencies: + "@types/http-errors" "*" + "@types/mime" "*" + "@types/node" "*" + +"@types/shelljs@^0.8.15": + version "0.8.15" + resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.15.tgz#22c6ab9dfe05cec57d8e6cb1a95ea173aee9fcac" + integrity sha512-vzmnCHl6hViPu9GNLQJ+DZFd6BQI2DBTUeOvYHqkWQLMfKAAQYMb/xAmZkTogZI/vqXHCWkqDRymDI5p0QTi5Q== + dependencies: + "@types/glob" "~7.2.0" + "@types/node" "*" + +"@types/uuid@^9.0.7": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.7.tgz#b14cebc75455eeeb160d5fe23c2fcc0c64f724d8" + integrity sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g== + +"@types/ws@^7.4.4": + version "7.4.7" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" + integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== + dependencies: + "@types/node" "*" + +"@types/ws@^8.5.10": + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + +"@typescript-eslint/eslint-plugin@^6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz#fc1ab5f23618ba590c87e8226ff07a760be3dd7b" + integrity sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.14.0" + "@typescript-eslint/type-utils" "6.14.0" + "@typescript-eslint/utils" "6.14.0" + "@typescript-eslint/visitor-keys" "6.14.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.4.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.14.0.tgz#a2d6a732e0d2b95c73f6a26ae7362877cc1b4212" + integrity sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA== + dependencies: + "@typescript-eslint/scope-manager" "6.14.0" + "@typescript-eslint/types" "6.14.0" + "@typescript-eslint/typescript-estree" "6.14.0" + "@typescript-eslint/visitor-keys" "6.14.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz#53d24363fdb5ee0d1d8cda4ed5e5321272ab3d48" + integrity sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg== + dependencies: + "@typescript-eslint/types" "6.14.0" + "@typescript-eslint/visitor-keys" "6.14.0" + +"@typescript-eslint/type-utils@6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz#ac9cb5ba0615c837f1a6b172feeb273d36e4f8af" + integrity sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw== + dependencies: + "@typescript-eslint/typescript-estree" "6.14.0" + "@typescript-eslint/utils" "6.14.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.14.0.tgz#935307f7a931016b7a5eb25d494ea3e1f613e929" + integrity sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA== + +"@typescript-eslint/typescript-estree@6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz#90c7ddd45cd22139adf3d4577580d04c9189ac13" + integrity sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw== + dependencies: + "@typescript-eslint/types" "6.14.0" + "@typescript-eslint/visitor-keys" "6.14.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.14.0.tgz#856a9e274367d99ffbd39c48128b93a86c4261e3" + integrity sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.14.0" + "@typescript-eslint/types" "6.14.0" + "@typescript-eslint/typescript-estree" "6.14.0" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz#1d1d486581819287de824a56c22f32543561138e" + integrity sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw== + dependencies: + "@typescript-eslint/types" "6.14.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== + +aes-js@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" + integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-includes@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" + integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.findlastindex@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" + integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + +array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bech32@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +blakejs@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" + integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== + +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +body-parser@^1.20.1: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browserify-aes@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +bs58check@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + +buffer@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +builtins@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" + integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== + dependencies: + semver "^7.0.0" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +command-exists@^1.2.8: + version "1.2.9" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" + integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== + +commander@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" + integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== + +commander@^2.20.3: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +connect@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" + integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +default-user-agent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-user-agent/-/default-user-agent-1.0.0.tgz#16c46efdcaba3edc45f24f2bd4868b01b7c2adc6" + integrity sha512-bDF7bg6OSNcSwFWPu4zYKpVkJZQYVrAANMYB8bc9Szem1D0yKdm4sa/rOCs2aC9+2GMqQ7KnwtZRvDhmLF0dXw== + dependencies: + os-name "~1.0.3" + +define-data-property@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" + integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delay@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== + +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0, destroy@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +digest-header@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/digest-header/-/digest-header-1.1.0.tgz#e16ab6cf4545bc4eea878c8c35acd1b89664d800" + integrity sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +elliptic@6.5.4, elliptic@^6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +es-abstract@^1.22.1: + version "1.22.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.2.tgz#90f7282d91d0ad577f505e423e52d4c1d93c1b8a" + integrity sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA== + dependencies: + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.2" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.1" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.12" + is-weakref "^1.0.2" + object-inspect "^1.12.3" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.11" + +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== + dependencies: + es6-promise "^4.0.3" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-standard-with-typescript@^39.1.1: + version "39.1.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-39.1.1.tgz#d682bd1fc8f1ee996940f85c9b0a833d7cfa5fee" + integrity sha512-t6B5Ep8E4I18uuoYeYxINyqcXb2UbC0SOOTxRtBSt2JUs+EzeXbfe2oaiPs71AIdnoWhXDO2fYOHz8df3kV84A== + dependencies: + "@typescript-eslint/parser" "^6.4.0" + eslint-config-standard "17.1.0" + +eslint-config-standard@17.1.0, eslint-config-standard@^17.1.0: + version "17.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz#40ffb8595d47a6b242e07cbfd49dc211ed128975" + integrity sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q== + +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== + dependencies: + debug "^3.2.7" + +eslint-plugin-es-x@^7.1.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es-x/-/eslint-plugin-es-x-7.3.0.tgz#c699280ad35cd315720c3cccf0fe503092c08788" + integrity sha512-W9zIs+k00I/I13+Bdkl/zG1MEO07G97XjUSQuH117w620SJ6bHtLUmoMvkGA2oYnI/gNdr+G7BONLyYnFaLLEQ== + dependencies: + "@eslint-community/eslint-utils" "^4.1.2" + "@eslint-community/regexpp" "^4.6.0" + +eslint-plugin-es@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" + integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== + dependencies: + eslint-utils "^2.0.0" + regexpp "^3.0.0" + +eslint-plugin-import@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz#8133232e4329ee344f2f612885ac3073b0b7e155" + integrity sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.14.2" + +eslint-plugin-n@^16.3.0: + version "16.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-16.3.0.tgz#8ad04e0c52b311d58bd9b6b59532e26a19d3911b" + integrity sha512-/XZLH5CUXGK3laz3xYFNza8ZxLCq8ZNW6MsVw5z3d5hc2AwZzi0fPiySFZHQTdVDOHGs2cGv91aqzWmgBdq2gQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + builtins "^5.0.1" + eslint-plugin-es-x "^7.1.0" + get-tsconfig "^4.7.0" + ignore "^5.2.4" + is-core-module "^2.12.1" + minimatch "^3.1.2" + resolve "^1.22.2" + semver "^7.5.3" + +eslint-plugin-node@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" + integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== + dependencies: + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-promise@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816" + integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig== + +eslint-plugin-standard@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz#0c3bf3a67e853f8bbbc580fb4945fbf16f41b7c5" + integrity sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.53.0: + version "8.53.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.53.0.tgz#14f2c8244298fcae1f46945459577413ba2697ce" + integrity sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.3" + "@eslint/js" "8.53.0" + "@humanwhocodes/config-array" "^0.11.13" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +ethereum-cryptography@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" + integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== + dependencies: + "@types/pbkdf2" "^3.0.0" + "@types/secp256k1" "^4.0.1" + blakejs "^1.1.0" + browserify-aes "^1.2.0" + bs58check "^2.1.2" + create-hash "^1.2.0" + create-hmac "^1.1.7" + hash.js "^1.1.7" + keccak "^3.0.0" + pbkdf2 "^3.0.17" + randombytes "^2.1.0" + safe-buffer "^5.1.2" + scrypt-js "^3.0.0" + secp256k1 "^4.0.1" + setimmediate "^1.0.5" + +ethereum-multicall@^2.16.1: + version "2.21.0" + resolved "https://registry.yarnpkg.com/ethereum-multicall/-/ethereum-multicall-2.21.0.tgz#a0dc5d5e7bbddabd484a75037ecb9b77ad5688ba" + integrity sha512-J234OuvUheTKvZVhMk41SwyB66m+MU+Xe2FFWOln8xu6TXKzOzsjSFQn/f5OTDGEiRStKMnJpCvQDim+Uk+qBQ== + dependencies: + "@ethersproject/providers" "^5.0.10" + ethers "^5.0.15" + +ethereumjs-util@^7.1.2, ethereumjs-util@^7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" + integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + rlp "^2.2.4" + +ethereumjs-wallet@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-1.0.2.tgz#2c000504b4c71e8f3782dabe1113d192522e99b6" + integrity sha512-CCWV4RESJgRdHIvFciVQFnCHfqyhXWchTPlkfp28Qc53ufs+doi5I/cV2+xeK9+qEo25XCWfP9MiL+WEPAZfdA== + dependencies: + aes-js "^3.1.2" + bs58check "^2.1.2" + ethereum-cryptography "^0.1.3" + ethereumjs-util "^7.1.2" + randombytes "^2.1.0" + scrypt-js "^3.0.1" + utf8 "^3.0.0" + uuid "^8.3.2" + +ethers@^5.0.15, ethers@^5.7.0, ethers@^5.7.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + +evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +express@^4.18.2: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +eyes@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" + integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.1.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.1.tgz#a02a15fdec25a8f844ff7cc658f03dd99eb4609b" + integrity sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + +follow-redirects@^1.12.1: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +form-data-encoder@^1.7.2: + version "1.9.0" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.9.0.tgz#fd18d316b1ec830d2a8be8ad86c1cf0317320b34" + integrity sha512-rahaRMkN8P8d/tgK/BLPX+WBVM27NbvdXBxqQujBtkDAIFspaRqN7Od7lfdGQA6KAD+f82fYCLBq1ipvcu8qLw== + +formdata-node@^4.3.3: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + +formstream@^1.1.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/formstream/-/formstream-1.3.1.tgz#6c6f53c1c09f0ffc43231022b355b1d5feda3016" + integrity sha512-FkW++ub+VbE5dpwukJVDizNWhSgp8FhmhI65pF7BZSVStBqe6Wgxe2Z9/Vhsn7l7nXCPwP+G1cyYlX8VwWOf0g== + dependencies: + destroy "^1.0.4" + mime "^2.5.2" + pause-stream "~0.0.11" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" + integrity sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + path-is-absolute "^1.0.0" + rimraf "^2.2.8" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +get-tsconfig@^4.7.0: + version "4.7.2" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.2.tgz#0dcd6fb330391d46332f4c6c1bf89a6514c2ddce" + integrity sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A== + dependencies: + resolve-pkg-maps "^1.0.0" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.0.0, glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.19.0: + version "13.23.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02" + integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== + dependencies: + get-intrinsic "^1.2.0" + has "^1.0.3" + side-channel "^1.0.4" + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +ioredis@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" + integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.12.1, is-core-module@^2.13.1: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + +jayson@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.0.tgz#60dc946a85197317f2b1439d672a8b0a99cea2f9" + integrity sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A== + dependencies: + "@types/connect" "^3.4.33" + "@types/node" "^12.12.54" + "@types/ws" "^7.4.4" + JSONStream "^1.3.5" + commander "^2.20.3" + delay "^5.0.0" + es6-promisify "^5.0.0" + eyes "^0.1.8" + isomorphic-ws "^4.0.1" + json-stringify-safe "^5.0.1" + uuid "^8.3.2" + ws "^7.4.5" + +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + integrity sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +keccak@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" + integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + readable-stream "^3.6.0" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + integrity sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw== + optionalDependencies: + graceful-fs "^4.1.9" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.5.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +node-addon-api@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== + +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-gyp-build@^4.2.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.1.tgz#24b6d075e5e391b8d5539d98c7fc5c210cac8a3e" + integrity sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ== + +object-inspect@^1.12.3, object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.fromentries@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +object.groupby@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" + integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + +object.values@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +os-name@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/os-name/-/os-name-1.0.3.tgz#1b379f64835af7c5a7f498b357cb95215c159edf" + integrity sha512-f5estLO2KN8vgtTRaILIgEGBoBrMnZ3JQ7W9TMZCnOIGwHe8TRGSpcagnWDo+Dfhd/z08k9Xe75hvciJJ8Qaew== + dependencies: + osx-release "^1.0.0" + win-release "^1.0.0" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +osx-release@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/osx-release/-/osx-release-1.1.0.tgz#f217911a28136949af1bf9308b241e2737d3cd6c" + integrity sha512-ixCMMwnVxyHFQLQnINhmIpWqXIfS2YOXchwQrk+OFzmo6nDjQ0E4KXAyyUh0T0MZgV4bUhkRrAbVqlE4yLVq4A== + dependencies: + minimist "^1.1.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pause-stream@~0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" + integrity sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A== + dependencies: + through "~2.3" + +pbkdf2@^3.0.17: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +qs@^6.11.2: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + +regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + +regexpp@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +require-from-string@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve@^1.1.6, resolve@^1.10.1, resolve@^1.22.2, resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^2.2.8: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rlp@^2.2.4: + version "2.2.7" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" + integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== + dependencies: + bn.js "^5.2.0" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scrypt-js@3.0.1, scrypt-js@^3.0.0, scrypt-js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + +secp256k1@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" + integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +semver@^5.0.1, semver@^5.5.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^6.1.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.0.0, semver@^7.5.3, semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0, serve-static@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +sha3@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.4.tgz#000fac0fe7c2feac1f48a25e7a31b52a6492cc8f" + integrity sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg== + dependencies: + buffer "6.0.3" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +solc@0.8.6: + version "0.8.6" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.6.tgz#e4341fa6780137df97b94a0cfbd59b3f2037d0e0" + integrity sha512-miiDaWdaUnD7A6Cktb/2ug9f+ajcOCDYRr7vgbPEsMoutSlBtp5rca57oMg8iHSuM7jilwdxePujWI/+rbNftQ== + dependencies: + command-exists "^1.2.8" + commander "3.0.2" + follow-redirects "^1.12.1" + fs-extra "^0.30.0" + js-sha3 "0.8.0" + memorystream "^0.3.1" + require-from-string "^2.0.0" + semver "^5.5.0" + tmp "0.0.33" + +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +"through@>=2.2.7 <3", through@~2.3: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tmp@0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + +tsconfig-paths@^3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^4.3.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.4.0.tgz#061cd10ff55664bb7174218cdf78c28c48f71c69" + integrity sha512-HT3RRs7sTfY22KuPQJkD/XjbTbxgP2Je5HPt6H6JEGvcjHd5Lqru75EbrP3tb4FYjNJ+DjLp+MNQTFQU0mhXNw== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + +typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +undici-types@~5.25.1: + version "5.25.3" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" + integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== + +undici@^5.22.1: + version "5.26.3" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.3.tgz#ab3527b3d5bb25b12f898dfd22165d472dd71b79" + integrity sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw== + dependencies: + "@fastify/busboy" "^2.0.0" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +urllib@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/urllib/-/urllib-3.21.0.tgz#1a81541d00121bf0c4a2c463cf4d8218d01a3a49" + integrity sha512-xtgaQ4G2emUssoz3jZjMTL2g32rEL75PggNHg3EUYf1h0sdkLSaRGAFgxe5UtNdFNoxJtjWk9hwNrY5Ja9fGNA== + dependencies: + default-user-agent "^1.0.0" + digest-header "^1.0.0" + form-data-encoder "^1.7.2" + formdata-node "^4.3.3" + formstream "^1.1.1" + mime-types "^2.1.35" + pump "^3.0.0" + qs "^6.11.2" + type-fest "^4.3.1" + undici "^5.22.1" + ylru "^1.3.2" + +utf8@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" + integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +win-release@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/win-release/-/win-release-1.1.1.tgz#5fa55e02be7ca934edfc12665632e849b72e5209" + integrity sha512-iCRnKVvGxOQdsKhcQId2PXV1vV3J/sDPXKA4Oe9+Eti2nb2ESEsYHRYls/UjoUW3bIc5ZDO8dTH50A/5iVN+bw== + dependencies: + semver "^5.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + +ws@^7.4.5: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +"ws@^8.15.0 ": + version "8.15.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.15.0.tgz#db080a279260c5f532fc668d461b8346efdfcf86" + integrity sha512-H/Z3H55mrcrgjFwI+5jKavgXvwQLtfPCUEp6pi35VhoB0pfcHnSoyuTzkBEZpzq49g1193CUEwIvmsjcotenYw== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +ylru@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.3.2.tgz#0de48017473275a4cbdfc83a1eaf67c01af8a785" + integrity sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/test/agent-test-data/proxyMainnet.json b/test/agent-test-data/proxyMainnet.json new file mode 100644 index 00000000..a25df61e --- /dev/null +++ b/test/agent-test-data/proxyMainnet.json @@ -0,0 +1,4799 @@ +{ + "message_proxy_mainnet_address": "0x8d0bb329E51Ea55D922b94EE8C15CBa53daAe9F9", + "message_proxy_mainnet_abi": [ + { + "type": "event", + "anonymous": false, + "name": "OutgoingMessage", + "inputs": [ + { + "type": "bytes32", + "name": "dstChainHash", + "indexed": true + }, + { + "type": "uint256", + "name": "msgCounter", + "indexed": true + }, + { + "type": "address", + "name": "srcContract", + "indexed": true + }, + { + "type": "address", + "name": "dstContract", + "indexed": false + }, + { + "type": "bytes", + "name": "data", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "PostMessageError", + "inputs": [ + { + "type": "uint256", + "name": "msgCounter", + "indexed": true + }, + { + "type": "bytes", + "name": "message", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "BASIC_POST_INCOMING_MESSAGES_TX", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "DEBUGGER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_CHAIN_ID", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MESSAGE_GAS_COST", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addConnectedChain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "communityPoolAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "connectedChains", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "incomingMessageCounter" + }, + { + "type": "uint256", + "name": "outgoingMessageCounter" + }, + { + "type": "bool", + "name": "inited" + } + ] + }, + { + "type": "function", + "name": "contractManagerOfSkaleManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getIncomingMessagesCounter", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "fromSchainName" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getOutgoingMessagesCounter", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "targetSchainName" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "incrementIncomingCounter", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractManagerOfSkaleManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isConnectedChain", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "isSchainOwner", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes32", + "name": "schainHash" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postIncomingMessages", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "fromSchainName" + }, + { + "type": "uint256", + "name": "startingCounter" + }, + { + "type": "tuple[]", + "name": "messages", + "components": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "address", + "name": "destinationContract" + }, + { + "type": "bytes", + "name": "data" + } + ] + }, + { + "type": "tuple", + "name": "sign", + "components": [ + { + "type": "uint256[2]", + "name": "blsSignature" + }, + { + "type": "uint256", + "name": "hashA" + }, + { + "type": "uint256", + "name": "hashB" + }, + { + "type": "uint256", + "name": "counter" + } + ] + }, + { + "type": "uint256", + "name": "" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "postOutgoingMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "dstChainHash" + }, + { + "type": "address", + "name": "dstContract" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "removeConnectedChain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setCommunityPool", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newCommunityPoolAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setCountersToZero", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + } + ], + "linker_address": "0x347907c4111b2122a8Aca6A81ac1c5Ad94458Cf9", + "linker_abi": [ + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "LINKER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "contractOnSchain" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "allowInterchainConnections", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "connectSchain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address[]", + "name": "schainContracts" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "contractManagerOfSkaleManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasMainnetContract", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "mainnetContract" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasSchain", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "connected" + } + ] + }, + { + "type": "function", + "name": "hasSchainContract", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "messageProxyAddress" + }, + { + "type": "address", + "name": "newContractManagerOfSkaleManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newContractManagerOfSkaleManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "interchainConnections", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "isNotKilled", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "schainHash" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "isSchainOwner", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes32", + "name": "schainHash" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "kill", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "registerMainnetContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newMainnetContract" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "removeMainnetContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "mainnetContract" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "removeSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainLinks", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "statuses", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "uint8", + "name": "" + } + ] + }, + { + "type": "function", + "name": "disconnectSchain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + } + ], + "community_pool_address": "0x744da9064B20B7574BD697b4c5B37C2578a95282", + "community_pool_abi": [ + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "LINKER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "contractOnSchain" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "contractManagerOfSkaleManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getBalance", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasSchainContract", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractManagerOfSkaleManager" + }, + { + "type": "address", + "name": "linker" + }, + { + "type": "address", + "name": "newMessageProxy" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newContractManagerOfSkaleManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isSchainOwner", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes32", + "name": "schainHash" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "minTransactionGas", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "rechargeUserWallet", + "constant": false, + "stateMutability": "payable", + "payable": true, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "refundGasByUser", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "schainHash" + }, + { + "type": "address", + "name": "node" + }, + { + "type": "address", + "name": "user" + }, + { + "type": "uint256", + "name": "gas" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "removeSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainLinks", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "withdrawFunds", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + } + ], + "deposit_box_eth_address": "0xF1E85FC6a94d4C8Fbb739c29d36C2E447ef1a315", + "deposit_box_eth_abi": [ + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "DEPOSIT_BOX_MANAGER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManagerEthAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "approveTransfers", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "contractManagerOfSkaleManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "deposit", + "constant": false, + "stateMutability": "payable", + "payable": true, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "to" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getFunds", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "receiver" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getMyEth", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasSchainContract", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractManagerOfSkaleManager" + }, + { + "type": "address", + "name": "linker" + }, + { + "type": "address", + "name": "messageProxy" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newContractManagerOfSkaleManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isSchainOwner", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes32", + "name": "schainHash" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "linker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "schainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removeSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "tokenManagerEthAddresses", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferredAmount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + } + ], + "deposit_box_erc20_address": "0x38BA6c9F431Ad54893E40D59a33313f1Ea05d688", + "deposit_box_erc20_abi": [ + { + "type": "event", + "anonymous": false, + "name": "ERC20TokenAdded", + "inputs": [ + { + "type": "string", + "name": "schainName", + "indexed": false + }, + { + "type": "address", + "name": "contractOnMainnet", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC20TokenReady", + "inputs": [ + { + "type": "address", + "name": "contractOnMainnet", + "indexed": true + }, + { + "type": "uint256", + "name": "amount", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "DEPOSIT_BOX_MANAGER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addERC20TokenByOwner", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc20OnMainnet" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "addSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManagerERC20Address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "contractManagerOfSkaleManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "depositERC20", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "disableWhitelist", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "enableWhitelist", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getFunds", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc20OnMainnet" + }, + { + "type": "address", + "name": "receiver" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSchainToERC20", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc20OnMainnet" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasSchainContract", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractManagerOfSkaleManager" + }, + { + "type": "address", + "name": "linker" + }, + { + "type": "address", + "name": "messageProxy" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newContractManagerOfSkaleManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isSchainOwner", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes32", + "name": "schainHash" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "linker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "schainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removeSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainToERC20", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + }, + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerERC20Addresses", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferredAmount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + }, + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "withoutWhitelist", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + } + ], + "deposit_box_erc721_address": "0xA0f10D670E9BE483D6b9d60FE44921E979D11AF1", + "deposit_box_erc721_abi": [ + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenAdded", + "inputs": [ + { + "type": "string", + "name": "schainName", + "indexed": false + }, + { + "type": "address", + "name": "contractOnMainnet", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenReady", + "inputs": [ + { + "type": "address", + "name": "contractOnMainnet", + "indexed": true + }, + { + "type": "uint256", + "name": "tokenId", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "DEPOSIT_BOX_MANAGER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addERC721TokenByOwner", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc721OnMainnet" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "addSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManagerERC721Address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "contractManagerOfSkaleManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "depositERC721", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "disableWhitelist", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "enableWhitelist", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getFunds", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc721OnMainnet" + }, + { + "type": "address", + "name": "receiver" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSchainToERC721", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc721OnMainnet" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasSchainContract", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractManagerOfSkaleManager" + }, + { + "type": "address", + "name": "linker" + }, + { + "type": "address", + "name": "messageProxy" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newContractManagerOfSkaleManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isSchainOwner", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes32", + "name": "schainHash" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "linker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "schainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removeSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainToERC721", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + }, + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerERC721Addresses", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferredAmount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "" + }, + { + "type": "uint256", + "name": "" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "withoutWhitelist", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + } + ], + "deposit_box_erc1155_address": "0x33676fc32E4B191633a5DC44939E7B66744De670", + "deposit_box_erc1155_abi": [ + { + "type": "event", + "anonymous": false, + "name": "ERC1155TokenAdded", + "inputs": [ + { + "type": "string", + "name": "schainName", + "indexed": false + }, + { + "type": "address", + "name": "contractOnMainnet", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC1155TokenReady", + "inputs": [ + { + "type": "address", + "name": "contractOnMainnet", + "indexed": true + }, + { + "type": "uint256[]", + "name": "ids", + "indexed": false + }, + { + "type": "uint256[]", + "name": "amounts", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "DEPOSIT_BOX_MANAGER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addERC1155TokenByOwner", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc1155OnMainnet" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "addSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManagerERC1155Address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "contractManagerOfSkaleManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "depositERC1155", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "id" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "depositERC1155Batch", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256[]", + "name": "ids" + }, + { + "type": "uint256[]", + "name": "amounts" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "disableWhitelist", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "enableWhitelist", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSchainToERC1155", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc1155OnMainnet" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasSchainContract", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractManagerOfSkaleManager" + }, + { + "type": "address", + "name": "linker" + }, + { + "type": "address", + "name": "messageProxy" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newContractManagerOfSkaleManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isSchainOwner", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes32", + "name": "schainHash" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "linker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "onERC1155BatchReceived", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "operator" + }, + { + "type": "address", + "name": "" + }, + { + "type": "uint256[]", + "name": "" + }, + { + "type": "uint256[]", + "name": "" + }, + { + "type": "bytes", + "name": "" + } + ], + "outputs": [ + { + "type": "bytes4", + "name": "" + } + ] + }, + { + "type": "function", + "name": "onERC1155Received", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "operator" + }, + { + "type": "address", + "name": "" + }, + { + "type": "uint256", + "name": "" + }, + { + "type": "uint256", + "name": "" + }, + { + "type": "bytes", + "name": "" + } + ], + "outputs": [ + { + "type": "bytes4", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "schainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "address", + "name": "receiver" + } + ] + }, + { + "type": "function", + "name": "removeSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainToERC1155", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + }, + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "supportsInterface", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes4", + "name": "interfaceId" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerERC1155Addresses", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "withoutWhitelist", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + } + ], + "deposit_box_erc721_with_metadata_address": "0xA0f10D670E9BE483D6b9d60FE44921E979D11AF1", + "deposit_box_erc721_with_metadata_abi": [ + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenAdded", + "inputs": [ + { + "type": "string", + "name": "schainName", + "indexed": false + }, + { + "type": "address", + "name": "contractOnMainnet", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenReady", + "inputs": [ + { + "type": "address", + "name": "contractOnMainnet", + "indexed": true + }, + { + "type": "uint256", + "name": "tokenId", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "DEPOSIT_BOX_MANAGER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addERC721TokenByOwner", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc721OnMainnet" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "addSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManagerERC721Address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "contractManagerOfSkaleManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "depositERC721", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "disableWhitelist", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "enableWhitelist", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getFunds", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc721OnMainnet" + }, + { + "type": "address", + "name": "receiver" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSchainToERC721", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "erc721OnMainnet" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasSchainContract", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractManagerOfSkaleManager" + }, + { + "type": "address", + "name": "linker" + }, + { + "type": "address", + "name": "messageProxy" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newContractManagerOfSkaleManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isSchainOwner", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes32", + "name": "schainHash" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "linker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "schainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removeSchainContract", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainToERC721", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + }, + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerERC721Addresses", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferredAmount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "" + }, + { + "type": "uint256", + "name": "" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "withoutWhitelist", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/agent-test-data/proxySchain_Bob.json b/test/agent-test-data/proxySchain_Bob.json new file mode 100644 index 00000000..88762a1c --- /dev/null +++ b/test/agent-test-data/proxySchain_Bob.json @@ -0,0 +1,7826 @@ +{ + "message_proxy_chain_address": "0x3d3f7076dfa10efCB763be53cfA000deDad9fE69", + "message_proxy_chain_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "OutgoingMessage", + "inputs": [ + { + "type": "bytes32", + "name": "dstChainHash", + "indexed": true + }, + { + "type": "uint256", + "name": "msgCounter", + "indexed": true + }, + { + "type": "address", + "name": "srcContract", + "indexed": true + }, + { + "type": "address", + "name": "dstContract", + "indexed": false + }, + { + "type": "bytes", + "name": "data", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "PostMessageError", + "inputs": [ + { + "type": "uint256", + "name": "msgCounter", + "indexed": true + }, + { + "type": "bytes", + "name": "message", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "CHAIN_CONNECTOR_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "DEBUGGER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "SKALE_FEATURES_SETTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addConnectedChain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "connectedChains", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "incomingMessageCounter" + }, + { + "type": "uint256", + "name": "outgoingMessageCounter" + }, + { + "type": "bool", + "name": "inited" + } + ] + }, + { + "type": "function", + "name": "getIncomingMessagesCounter", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "fromSchainName" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getOutgoingMessagesCounter", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "targetSchainName" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSkaleFeatures", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "isConnectedChain", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "mainnetConnected", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "moveIncomingCounter", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "postIncomingMessages", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "fromChainName" + }, + { + "type": "uint256", + "name": "startingCounter" + }, + { + "type": "tuple[]", + "name": "messages", + "components": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "address", + "name": "destinationContract" + }, + { + "type": "bytes", + "name": "data" + } + ] + }, + { + "type": "tuple", + "name": "signature", + "components": [ + { + "type": "uint256[2]", + "name": "blsSignature" + }, + { + "type": "uint256", + "name": "hashA" + }, + { + "type": "uint256", + "name": "hashB" + }, + { + "type": "uint256", + "name": "counter" + } + ] + }, + { + "type": "uint256", + "name": "idxLastToPopNotIncluding" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "postOutgoingMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "targetSchainName" + }, + { + "type": "address", + "name": "dstContract" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "removeConnectedChain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setCountersToZero", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setSkaleFeaturesAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newSkaleFeaturesAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "skaleFeaturesAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "verifyOutgoingMessageData", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "tuple", + "name": "message", + "components": [ + { + "type": "string", + "name": "dstChain" + }, + { + "type": "uint256", + "name": "msgCounter" + }, + { + "type": "address", + "name": "srcContract" + }, + { + "type": "address", + "name": "dstContract" + }, + { + "type": "bytes", + "name": "data" + } + ] + } + ], + "outputs": [ + { + "type": "bool", + "name": "isValidMessage" + } + ] + } + ], + "token_manager_linker_address": "0x1B915D803Ac4070937DB51B5B8c0A6545dB22267", + "token_manager_linker_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newMessageProxyAddress" + }, + { + "type": "address", + "name": "newLinkerAddress" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "InterchainConnectionAllowed", + "inputs": [ + { + "type": "bool", + "name": "isAllowed", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_HASH", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_NAME", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "REGISTRAR_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "SKALE_FEATURES_SETTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "connectSchain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address[]", + "name": "tokenManagerAddresses" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "disconnectSchain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getLinkerAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getMessageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSkaleFeatures", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasSchain", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "connected" + } + ] + }, + { + "type": "function", + "name": "hasTokenManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "tokenManager" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "interchainConnections", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "linkerAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "fromChainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "registerTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newTokenManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "removeTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "tokenManagerAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setSkaleFeaturesAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newSkaleFeaturesAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "skaleFeaturesAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + } + ], + "community_locker_address": "0x3f5A6C1B90a99AE200504fC6Aa14D8809EFc5271", + "community_locker_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "newSchainName" + }, + { + "type": "address", + "name": "newMessageProxy" + }, + { + "type": "address", + "name": "newIMALinker" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "UserUnfrozed", + "inputs": [ + { + "type": "bytes32", + "name": "schainHash", + "indexed": false + }, + { + "type": "address", + "name": "user", + "indexed": false + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_HASH", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_NAME", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "SKALE_FEATURES_SETTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "checkAllowedToSendMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "receiver" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getMessageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSkaleFeatures", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getTokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "fromChainHash" + }, + { + "type": "address", + "name": "" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "setSkaleFeaturesAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newSkaleFeaturesAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setTimeLimitPerMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "newTimeLimitPerMessage" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "skaleFeaturesAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "timeLimitPerMessage", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + } + ], + "token_manager_eth_address": "0x7CFD4D21586222f92BD8c7C01928F912B83971C4", + "token_manager_eth_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "newChainName" + }, + { + "type": "address", + "name": "newMessageProxy" + }, + { + "type": "address", + "name": "newIMALinker" + }, + { + "type": "address", + "name": "newCommunityLocker" + }, + { + "type": "address", + "name": "newDepositBox" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_HASH", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_NAME", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "SKALE_FEATURES_SETTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "automaticDeploy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "changeDepositBoxAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newDepositBox" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "communityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "depositBox", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "disableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "enableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "exitToMain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getCommunityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getDepositBoxEthAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getEthErc20Address", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getMessageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSchainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSkaleFeatures", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getTokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasTokenManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "fromChainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removeTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "setEthErc20Address", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newEthERC20Address" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setSkaleFeaturesAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newSkaleFeaturesAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "skaleFeaturesAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagers", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferToSchain", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "targetSchainName" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + } + ], + "token_manager_erc20_address": "0x25edbe3515f087F2E531aDB33f5133907676b71f", + "token_manager_erc20_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "newChainName" + }, + { + "type": "address", + "name": "newMessageProxy" + }, + { + "type": "address", + "name": "newIMALinker" + }, + { + "type": "address", + "name": "newCommunityLocker" + }, + { + "type": "address", + "name": "newDepositBox" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC20TokenAdded", + "inputs": [ + { + "type": "address", + "name": "erc20OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc20OnSchain", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC20TokenCreated", + "inputs": [ + { + "type": "address", + "name": "erc20OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc20OnSchain", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC20TokenReceived", + "inputs": [ + { + "type": "address", + "name": "erc20OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc20OnSchain", + "indexed": true + }, + { + "type": "uint256", + "name": "amount", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_HASH", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_NAME", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "SKALE_FEATURES_SETTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addERC20TokenByOwner", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "erc20OnMainnet" + }, + { + "type": "address", + "name": "erc20OnSchain" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "addTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "automaticDeploy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "changeDepositBoxAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newDepositBox" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "clonesErc20", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "communityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "depositBox", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "disableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "enableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "exitToMainERC20", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getCommunityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getDepositBoxERC20Address", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getMessageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSchainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSkaleFeatures", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getTokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasTokenManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "fromChainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removeTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "setSkaleFeaturesAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newSkaleFeaturesAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "skaleFeaturesAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagers", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "totalSupplyOnMainnet", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferToSchainERC20", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "targetSchainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + } + ], + "token_manager_erc721_address": "0xe0911b815F3df0AEf4259FC2F6ad211c76F65180", + "token_manager_erc721_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "newChainName" + }, + { + "type": "address", + "name": "newMessageProxy" + }, + { + "type": "address", + "name": "newIMALinker" + }, + { + "type": "address", + "name": "newCommunityLocker" + }, + { + "type": "address", + "name": "newDepositBox" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenAdded", + "inputs": [ + { + "type": "address", + "name": "erc721OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc721OnSchain", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenCreated", + "inputs": [ + { + "type": "address", + "name": "erc721OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc721OnSchain", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenReceived", + "inputs": [ + { + "type": "address", + "name": "erc721OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc721OnSchain", + "indexed": true + }, + { + "type": "uint256", + "name": "tokenId", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_HASH", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_NAME", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "SKALE_FEATURES_SETTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addERC721TokenByOwner", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "erc721OnMainnet" + }, + { + "type": "address", + "name": "erc721OnSchain" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "addTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "automaticDeploy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "changeDepositBoxAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newDepositBox" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "clonesErc721", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "communityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "depositBox", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "disableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "enableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "exitToMainERC721", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getCommunityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getDepositBoxERC721Address", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getMessageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSchainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSkaleFeatures", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getTokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasTokenManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "fromChainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removeTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "setSkaleFeaturesAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newSkaleFeaturesAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "skaleFeaturesAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagers", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferToSchainERC721", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "targetSchainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + } + ], + "token_manager_erc1155_address": "0x5702d81B9EADAf78AD7176B33F0875C19a905d79", + "token_manager_erc1155_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "newChainName" + }, + { + "type": "address", + "name": "newMessageProxy" + }, + { + "type": "address", + "name": "newIMALinker" + }, + { + "type": "address", + "name": "newCommunityLocker" + }, + { + "type": "address", + "name": "newDepositBox" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC1155TokenAdded", + "inputs": [ + { + "type": "address", + "name": "erc1155OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc1155OnSchain", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC1155TokenCreated", + "inputs": [ + { + "type": "address", + "name": "erc1155OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc1155OnSchain", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC1155TokenReceived", + "inputs": [ + { + "type": "address", + "name": "erc1155OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc1155OnSchain", + "indexed": true + }, + { + "type": "uint256[]", + "name": "ids", + "indexed": false + }, + { + "type": "uint256[]", + "name": "amounts", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_HASH", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_NAME", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "SKALE_FEATURES_SETTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addERC1155TokenByOwner", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "erc1155OnMainnet" + }, + { + "type": "address", + "name": "erc1155OnSchain" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "addTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "automaticDeploy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "changeDepositBoxAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newDepositBox" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "clonesErc1155", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "communityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "depositBox", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "disableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "enableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "exitToMainERC1155", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "id" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "exitToMainERC1155Batch", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256[]", + "name": "ids" + }, + { + "type": "uint256[]", + "name": "amounts" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getCommunityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getDepositBoxERC1155Address", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getMessageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSchainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSkaleFeatures", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getTokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasTokenManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "fromChainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removeTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "setSkaleFeaturesAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newSkaleFeaturesAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "skaleFeaturesAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagers", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferToSchainERC1155", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "targetSchainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "id" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "transferToSchainERC1155Batch", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "targetSchainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256[]", + "name": "ids" + }, + { + "type": "uint256[]", + "name": "amounts" + } + ], + "outputs": [] + } + ], + "token_manager_erc721_with_metadata_address": "0xe0911b815F3df0AEf4259FC2F6ad211c76F65180", + "token_manager_erc721_with_metadata_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "newChainName" + }, + { + "type": "address", + "name": "newMessageProxy" + }, + { + "type": "address", + "name": "newIMALinker" + }, + { + "type": "address", + "name": "newCommunityLocker" + }, + { + "type": "address", + "name": "newDepositBox" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenAdded", + "inputs": [ + { + "type": "address", + "name": "erc721OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc721OnSchain", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenCreated", + "inputs": [ + { + "type": "address", + "name": "erc721OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc721OnSchain", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ERC721TokenReceived", + "inputs": [ + { + "type": "address", + "name": "erc721OnMainnet", + "indexed": true + }, + { + "type": "address", + "name": "erc721OnSchain", + "indexed": true + }, + { + "type": "uint256", + "name": "tokenId", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_HASH", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MAINNET_NAME", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "SKALE_FEATURES_SETTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "addERC721TokenByOwner", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "erc721OnMainnet" + }, + { + "type": "address", + "name": "erc721OnSchain" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "addTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + }, + { + "type": "address", + "name": "newTokenManager" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "automaticDeploy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "changeDepositBoxAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newDepositBox" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "clonesErc721", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "communityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "depositBox", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "disableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "enableAutomaticDeploy", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "exitToMainERC721", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getCommunityLocker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getDepositBoxERC721Address", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getMessageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSchainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSkaleFeatures", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getTokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "hasTokenManager", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "messageProxy", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "postMessage", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "fromChainHash" + }, + { + "type": "address", + "name": "sender" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removeTokenManager", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "schainName" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "schainHash", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "setSkaleFeaturesAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newSkaleFeaturesAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "skaleFeaturesAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerLinker", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagers", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferToSchainERC721", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "string", + "name": "targetSchainName" + }, + { + "type": "address", + "name": "contractOnMainnet" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + } + ], + "eth_erc20_address": "0xC46846f9a913709d66C1f54C85F0100919e00CCC", + "eth_erc20_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "tokenManagerEthAddress" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "Approval", + "inputs": [ + { + "type": "address", + "name": "owner", + "indexed": true + }, + { + "type": "address", + "name": "spender", + "indexed": true + }, + { + "type": "uint256", + "name": "value", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "Transfer", + "inputs": [ + { + "type": "address", + "name": "from", + "indexed": true + }, + { + "type": "address", + "name": "to", + "indexed": true + }, + { + "type": "uint256", + "name": "value", + "indexed": false + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "SKALE_FEATURES_SETTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "allowance", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "owner" + }, + { + "type": "address", + "name": "spender" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "approve", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "spender" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "balanceOf", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "burn", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "burnFrom", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "decimals", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "uint8", + "name": "" + } + ] + }, + { + "type": "function", + "name": "decreaseAllowance", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "spender" + }, + { + "type": "uint256", + "name": "subtractedValue" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getSkaleFeatures", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getTokenManagerEthAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "increaseAllowance", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "spender" + }, + { + "type": "uint256", + "name": "addedValue" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "mint", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "name", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setSkaleFeaturesAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "newSkaleFeaturesAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "skaleFeaturesAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "symbol", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenManagerEth", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "totalSupply", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transfer", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "recipient" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferFrom", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "address", + "name": "recipient" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + } + ], + "ERC20OnChain_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "contractName" + }, + { + "type": "string", + "name": "contractSymbol" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "Approval", + "inputs": [ + { + "type": "address", + "name": "owner", + "indexed": true + }, + { + "type": "address", + "name": "spender", + "indexed": true + }, + { + "type": "uint256", + "name": "value", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "Transfer", + "inputs": [ + { + "type": "address", + "name": "from", + "indexed": true + }, + { + "type": "address", + "name": "to", + "indexed": true + }, + { + "type": "uint256", + "name": "value", + "indexed": false + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MINTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "allowance", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "owner" + }, + { + "type": "address", + "name": "spender" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "approve", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "spender" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "balanceOf", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "burn", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "burnFrom", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "decimals", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "uint8", + "name": "" + } + ] + }, + { + "type": "function", + "name": "decreaseAllowance", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "spender" + }, + { + "type": "uint256", + "name": "subtractedValue" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "increaseAllowance", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "spender" + }, + { + "type": "uint256", + "name": "addedValue" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "mint", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256", + "name": "value" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "name", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "symbol", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "totalSupply", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transfer", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "recipient" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferFrom", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "sender" + }, + { + "type": "address", + "name": "recipient" + }, + { + "type": "uint256", + "name": "amount" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + } + ], + "ERC721OnChain_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "contractName" + }, + { + "type": "string", + "name": "contractSymbol" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "Approval", + "inputs": [ + { + "type": "address", + "name": "owner", + "indexed": true + }, + { + "type": "address", + "name": "approved", + "indexed": true + }, + { + "type": "uint256", + "name": "tokenId", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ApprovalForAll", + "inputs": [ + { + "type": "address", + "name": "owner", + "indexed": true + }, + { + "type": "address", + "name": "operator", + "indexed": true + }, + { + "type": "bool", + "name": "approved", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "Transfer", + "inputs": [ + { + "type": "address", + "name": "from", + "indexed": true + }, + { + "type": "address", + "name": "to", + "indexed": true + }, + { + "type": "uint256", + "name": "tokenId", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MINTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "approve", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "balanceOf", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "owner" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "baseURI", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "burn", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getApproved", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "isApprovedForAll", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "owner" + }, + { + "type": "address", + "name": "operator" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "mint", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "name", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "ownerOf", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "safeTransferFrom", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "from" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "safeTransferFrom", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "from" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + }, + { + "type": "bytes", + "name": "_data" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setApprovalForAll", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "operator" + }, + { + "type": "bool", + "name": "approved" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setTokenURI", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "tokenId" + }, + { + "type": "string", + "name": "tokenUri" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "supportsInterface", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes4", + "name": "interfaceId" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "symbol", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenByIndex", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenOfOwnerByIndex", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "owner" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "tokenURI", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + }, + { + "type": "function", + "name": "totalSupply", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferFrom", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "from" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "tokenId" + } + ], + "outputs": [] + } + ], + "ERC1155OnChain_abi": [ + { + "type": "constructor", + "payable": false, + "inputs": [ + { + "type": "string", + "name": "uri" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "ApprovalForAll", + "inputs": [ + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "operator", + "indexed": true + }, + { + "type": "bool", + "name": "approved", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleAdminChanged", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "bytes32", + "name": "previousAdminRole", + "indexed": true + }, + { + "type": "bytes32", + "name": "newAdminRole", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleGranted", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RoleRevoked", + "inputs": [ + { + "type": "bytes32", + "name": "role", + "indexed": true + }, + { + "type": "address", + "name": "account", + "indexed": true + }, + { + "type": "address", + "name": "sender", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "TransferBatch", + "inputs": [ + { + "type": "address", + "name": "operator", + "indexed": true + }, + { + "type": "address", + "name": "from", + "indexed": true + }, + { + "type": "address", + "name": "to", + "indexed": true + }, + { + "type": "uint256[]", + "name": "ids", + "indexed": false + }, + { + "type": "uint256[]", + "name": "values", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "TransferSingle", + "inputs": [ + { + "type": "address", + "name": "operator", + "indexed": true + }, + { + "type": "address", + "name": "from", + "indexed": true + }, + { + "type": "address", + "name": "to", + "indexed": true + }, + { + "type": "uint256", + "name": "id", + "indexed": false + }, + { + "type": "uint256", + "name": "value", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "URI", + "inputs": [ + { + "type": "string", + "name": "value", + "indexed": false + }, + { + "type": "uint256", + "name": "id", + "indexed": true + } + ] + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "MINTER_ROLE", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "balanceOf", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256", + "name": "id" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "balanceOfBatch", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address[]", + "name": "accounts" + }, + { + "type": "uint256[]", + "name": "ids" + } + ], + "outputs": [ + { + "type": "uint256[]", + "name": "" + } + ] + }, + { + "type": "function", + "name": "burn", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256", + "name": "id" + }, + { + "type": "uint256", + "name": "value" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "burnBatch", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256[]", + "name": "ids" + }, + { + "type": "uint256[]", + "name": "values" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "getRoleAdmin", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "bytes32", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMember", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "uint256", + "name": "index" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "getRoleMemberCount", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "" + } + ] + }, + { + "type": "function", + "name": "grantRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "hasRole", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "isApprovedForAll", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "address", + "name": "operator" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "mint", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256", + "name": "id" + }, + { + "type": "uint256", + "name": "amount" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "mintBatch", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "account" + }, + { + "type": "uint256[]", + "name": "ids" + }, + { + "type": "uint256[]", + "name": "amounts" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "renounceRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "revokeRole", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "bytes32", + "name": "role" + }, + { + "type": "address", + "name": "account" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "safeBatchTransferFrom", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "from" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256[]", + "name": "ids" + }, + { + "type": "uint256[]", + "name": "amounts" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "safeTransferFrom", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "from" + }, + { + "type": "address", + "name": "to" + }, + { + "type": "uint256", + "name": "id" + }, + { + "type": "uint256", + "name": "amount" + }, + { + "type": "bytes", + "name": "data" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setApprovalForAll", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "operator" + }, + { + "type": "bool", + "name": "approved" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "supportsInterface", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "bytes4", + "name": "interfaceId" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "uri", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "" + } + ], + "outputs": [ + { + "type": "string", + "name": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/agent-test.mjs b/test/agent-test.mjs new file mode 100644 index 00000000..6e1673e3 --- /dev/null +++ b/test/agent-test.mjs @@ -0,0 +1,1242 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file agent-test.mjs + * @copyright SKALE Labs 2019-Present + */ + +import * as assert from "assert"; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import * as url from "url"; + +import * as owaspUtils from "../src/build/owaspUtils.js"; +import * as imaTx from "../src/build/imaTx.js"; +import * as log from "../src/build/log.js"; +import * as imaUtils from "../src/build/utils.js"; +import * as imaCLI from "../src/build/cli.js"; + +import * as state from "../src/build/state.js"; + +const __dirname = path.dirname( url.fileURLToPath( import.meta.url ) ); +const __filename = new URL( "", import.meta.url ).pathname; + +process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; + +log.exposeDetailsSet( false ); +log.verboseSet( log.verboseParse( "info" ) ); + +log.removeAll(); +const imaState = { + "loopState": { + "oracle": { + "isInProgress": false, + "wasInProgress": false + }, + "m2s": { + "isInProgress": false, + "wasInProgress": false + }, + "s2m": { + "isInProgress": false, + "wasInProgress": false + }, + "s2s": { + "isInProgress": false, + "wasInProgress": false + } + }, + + "strLogFilePath": "", + "nLogMaxSizeBeforeRotation": -1, + "nLogMaxFilesCount": -1, + "isPrintGathered": true, + "isPrintSecurityValues": false, + "isPrintPWA": false, + "isDynamicLogInDoTransfer": true, + "isDynamicLogInBlsSigner": false, + + "bIsNeededCommonInit": true, + "bSignMessages": false, + "joSChainNetworkInfo": null, + "strPathBlsGlue": "", + "strPathHashG1": "", + "strPathBlsVerify": "", + + "bShowConfigMode": false, + + "isEnabledMultiCall": true, + + "bNoWaitSChainStarted": false, + "nMaxWaitSChainAttempts": 0 + Number.MAX_SAFE_INTEGER, // 20 + + "nAmountOfWei": 0, + "nAmountOfToken": 0, + "arrAmountsOfTokens": null, + "idToken": 0, + "idTokens": [], + "haveOneTokenIdentifier": false, + "haveArrayOfTokenIdentifiers": false, + + "nTransferBlockSizeM2S": 4, + "nTransferBlockSizeS2M": 4, + "nTransferBlockSizeS2S": 4, + "nTransferStepsM2S": 8, + "nTransferStepsS2M": 8, + "nTransferStepsS2S": 8, + "nMaxTransactionsM2S": 0, + "nMaxTransactionsS2M": 0, + "nMaxTransactionsS2S": 0, + + "nBlockAwaitDepthM2S": 0, + "nBlockAwaitDepthS2M": 0, + "nBlockAwaitDepthS2S": 0, + "nBlockAgeM2S": 0, + "nBlockAgeS2M": 0, + "nBlockAgeS2S": 0, + + "nLoopPeriodSeconds": 10, + + "nNodeNumber": 0, // S-Chain node number(zero based) + "nNodesCount": 1, + "nTimeFrameSeconds": 0, // 0-disable, 60-recommended + "nNextFrameGap": 10, + + "nAutoExitAfterSeconds": 0, // 0-disable + + "joDepositBoxETH": null, // only main net + "joDepositBoxERC20": null, // only main net + "joDepositBoxERC721": null, // only main net + "joDepositBoxERC1155": null, // only main net + "joTokenManager": null, // only s-chain + "joMessageProxyMainNet": null, + "joMessageProxySChain": null, + "joLinker": null, + "joLockAndDataSChain": null, + "joEthErc20": null, // only s-chain + + "chainProperties": { + "mn": { + "joAccount": { + "privateKey": owaspUtils.toEthPrivateKey( process.env.PRIVATE_KEY_FOR_ETHEREUM ), + "address": function () { return owaspUtils.fnAddressImpl_( this ); }, + "strTransactionManagerURL": + owaspUtils.toStringURL( process.env.TRANSACTION_MANAGER_URL_ETHEREUM ), + "nTmPriority": + owaspUtils.toStringURL( + process.env.TRANSACTION_MANAGER_PRIORITY_ETHEREUM ) || 5, + "strSgxURL": owaspUtils.toStringURL( process.env.SGX_URL_ETHEREUM ), + "strSgxKeyName": owaspUtils.toStringURL( process.env.SGX_KEY_ETHEREUM ), + "strPathSslKey": + ( process.env.SGX_SSL_KEY_FILE_ETHEREUM || "" ).toString().trim(), + "strPathSslCert": + ( process.env.SGX_SSL_CERT_FILE_ETHEREUM || "" ).toString().trim(), + "strBlsKeyName": owaspUtils.toStringURL( process.env.BLS_KEY_ETHEREUM ) + }, + "transactionCustomizer": imaTx.getTransactionCustomizerForMainNet(), + "ethersProvider": null, + "strURL": + owaspUtils.toStringURL( process.env.URL_W3_ETHEREUM || "http://127.0.0.1:8545" ), + "strChainName": ( process.env.CHAIN_NAME_ETHEREUM || "Mainnet" ).toString().trim(), + "chainId": owaspUtils.toInteger( process.env.CID_ETHEREUM ) || -4, + "strPathAbiJson": imaUtils.normalizePath( "./agent-test-data/proxyMainnet.json" ), + "joAbiIMA": { }, + "bHaveAbiIMA": false, + "joErc20": null, + "joErc721": null, + "joErc1155": null, + "strCoinNameErc20": "", // in-JSON coin name + "strCoinNameErc721": "", // in-JSON coin name + "strCoinNameErc1155": "", // in-JSON coin name + "strPathJsonErc20": "", + "strPathJsonErc721": "", + "strPathJsonErc1155": "" + }, + "sc": { + "joAccount": { + "privateKey": owaspUtils.toEthPrivateKey( process.env.PRIVATE_KEY_FOR_SCHAIN ), + "address": function () { return owaspUtils.fnAddressImpl_( this ); }, + "strTransactionManagerURL": + owaspUtils.toStringURL( process.env.TRANSACTION_MANAGER_URL_S_CHAIN ), + "nTmPriority": + owaspUtils.toStringURL( + process.env.TRANSACTION_MANAGER_PRIORITY_S_CHAIN ) || 5, + "strSgxURL": owaspUtils.toStringURL( process.env.SGX_URL_S_CHAIN ), + "strSgxKeyName": owaspUtils.toStringURL( process.env.SGX_KEY_S_CHAIN ), + "strPathSslKey": + ( process.env.SGX_SSL_KEY_FILE_S_CHAIN || "" ).toString().trim(), + "strPathSslCert": + ( process.env.SGX_SSL_CERT_FILE_S_CHAIN || "" ).toString().trim(), + "strBlsKeyName": owaspUtils.toStringURL( process.env.BLS_KEY_S_CHAIN ) + }, + "transactionCustomizer": imaTx.getTransactionCustomizerForSChain(), + "ethersProvider": null, + "strURL": + owaspUtils.toStringURL( process.env.URL_W3_S_CHAIN || "http://127.0.0.1:15000" ), + "strChainName": ( process.env.CHAIN_NAME_SCHAIN || "Bob" ).toString().trim(), + "chainId": owaspUtils.toInteger( process.env.CID_SCHAIN ) || -4, + "strPathAbiJson": imaUtils.normalizePath( "./agent-test-data/proxySchain_Bob.json" ), + "joAbiIMA": { }, + "bHaveAbiIMA": false, + "joErc20": null, + "joErc721": null, + "joErc1155": null, + "strCoinNameErc20": "", // in-JSON coin name + "strCoinNameErc721": "", // in-JSON coin name + "strCoinNameErc1155": "", // in-JSON coin name + "strPathJsonErc20": "", + "strPathJsonErc721": "", + "strPathJsonErc1155": "" + }, + "tc": { + "joAccount": { + "privateKey": + owaspUtils.toEthPrivateKey( process.env.PRIVATE_KEY_FOR_SCHAIN_TARGET ), + "address": function () { return owaspUtils.fnAddressImpl_( this ); }, + "strTransactionManagerURL": + owaspUtils.toStringURL( process.env.TRANSACTION_MANAGER_URL_S_CHAIN_TARGET ), + "nTmPriority": + owaspUtils.toStringURL( + process.env.TRANSACTION_MANAGER_PRIORITY_S_CHAIN_TARGET ) || 5, + "strSgxURL": owaspUtils.toStringURL( process.env.SGX_URL_S_CHAIN_TARGET ), + "strSgxKeyName": owaspUtils.toStringURL( process.env.SGX_KEY_S_CHAIN_TARGET ), + "strPathSslKey": + ( process.env.SGX_SSL_KEY_FILE_S_CHAIN_TARGET || "" ).toString().trim(), + "strPathSslCert": + ( process.env.SGX_SSL_CERT_FILE_S_CHAIN_TARGET || "" ).toString().trim(), + "strBlsKeyName": owaspUtils.toStringURL( process.env.BLS_KEY_T_CHAIN ) + }, + "transactionCustomizer": imaTx.getTransactionCustomizerForSChainTarget(), + "ethersProvider": null, + "strURL": owaspUtils.toStringURL( process.env.URL_W3_S_CHAIN_TARGET ), + "strChainName": + ( process.env.CHAIN_NAME_SCHAIN_TARGET || "Alice" ).toString().trim(), + "chainId": owaspUtils.toInteger( process.env.CID_SCHAIN_TARGET ) || -4, + "strPathAbiJson": null, + "joAbiIMA": { }, + "bHaveAbiIMA": false, + "joErc20": null, + "joErc721": null, + "joErc1155": null, + "strCoinNameErc20": "", // in-JSON coin name + "strCoinNameErc721": "", // in-JSON coin name + "strCoinNameErc1155": "", // in-JSON coin name + "strPathJsonErc20": "", + "strPathJsonErc721": "", + "strPathJsonErc1155": "" + } + }, + + "strPathAbiJsonSkaleManager": null, + "joAbiSkaleManager": { }, + "bHaveSkaleManagerABI": false, + + "strChainNameOriginChain": + ( process.env.CHAIN_NAME_ETHEREUM || "Mainnet" ).toString().trim(), + + "strAddrErc20Explicit": "", + "strAddrErc20ExplicitTarget": "", // S<->S target + "strAddrErc721Explicit": "", + "strAddrErc721ExplicitTarget": "", // S<->S target + "strAddrErc1155Explicit": "", + "strAddrErc1155ExplicitTarget": "", // S<->S target + + "isPWA": true, + "nTimeoutSecondsPWA": 60, + + "optsS2S": { + "isEnabled": false, + "strNetworkBrowserPath": null + }, + + "nJsonRpcPort": 14999, // 0 to disable + "isCrossImaBlsMode": true, + + "arrActions": [] // array of actions to run +}; +state.set( imaState ); + +imaCLI.commonInit(); +imaCLI.initContracts(); + +describe( "OWASP-1", function() { + + describe( "Parsing utilities", function() { + + it( "Integer basic validation", function() { + assert.equal( owaspUtils.isNumeric( "0" ), true ); + assert.equal( owaspUtils.isNumeric( "123" ), true ); + } ); + + it( "Integer RegEx validation", function() { + assert.equal( owaspUtils.rxIsInt( "0" ), true ); + assert.equal( owaspUtils.rxIsInt( "123" ), true ); + assert.equal( owaspUtils.rxIsInt( "-456" ), true ); + assert.equal( owaspUtils.rxIsInt( "a012" ), false ); + } ); + + it( "Floating point RegEx validation", function() { + assert.equal( owaspUtils.rxIsFloat( "0" ), true ); + assert.equal( owaspUtils.rxIsFloat( "0.0" ), true ); + assert.equal( owaspUtils.rxIsFloat( "123.456" ), true ); + assert.equal( owaspUtils.rxIsFloat( "-123.456" ), true ); + assert.equal( owaspUtils.rxIsFloat( "a012" ), false ); + } ); + + it( "Radix validation", function() { + assert.equal( owaspUtils.validateRadix( "123", "10" ), 10 ); + assert.equal( owaspUtils.validateRadix( "0x20", "16" ), 16 ); + } ); + + it( "Integer conversion", function() { + assert.equal( owaspUtils.toInteger( "12345", 10 ), 12345 ); + assert.equal( owaspUtils.toInteger( "0x20", 16 ), 0x20 ); + } ); + + it( "Integer automatic conversion", function() { + assert.equal( owaspUtils.parseIntOrHex( 12345, 10 ), 12345 ); + assert.equal( owaspUtils.parseIntOrHex( "12345", 10 ), 12345 ); + assert.equal( owaspUtils.parseIntOrHex( 0x20, 16 ), 0x20 ); + assert.equal( owaspUtils.parseIntOrHex( "0x20", 16 ), 0x20 ); + } ); + + it( "Integer advanced validation", function() { + assert.equal( owaspUtils.validateInteger( "12345", 10 ), true ); + assert.equal( owaspUtils.validateInteger( "0x20", 16 ), true ); + assert.equal( owaspUtils.validateInteger( "hello 12345", 10 ), false ); + assert.equal( owaspUtils.validateInteger( "hello 0x20", 16 ), false ); + } ); + + it( "Floating point advanced validation", function() { + assert.equal( owaspUtils.validateFloat( "123.456" ), true ); + assert.equal( owaspUtils.validateFloat( "hello 123.456" ), false ); + } ); + + it( "Floating point conversion", function() { + assert.equal( owaspUtils.toFloat( "123.456" ), 123.456 ); + assert.equal( owaspUtils.rxIsFloat( owaspUtils.toFloat( "hello 123.456" ) ), false ); + } ); + + it( "Boolean conversion", function() { + assert.equal( owaspUtils.toBoolean( "true" ), true ); + assert.equal( owaspUtils.toBoolean( "false" ), false ); + assert.equal( owaspUtils.toBoolean( true ), true ); + assert.equal( owaspUtils.toBoolean( false ), false ); + assert.equal( owaspUtils.toBoolean( "True" ), true ); + assert.equal( owaspUtils.toBoolean( "False" ), false ); + assert.equal( owaspUtils.toBoolean( "TRUE" ), true ); + assert.equal( owaspUtils.toBoolean( "FALSE" ), false ); + assert.equal( owaspUtils.toBoolean( "t" ), true ); + assert.equal( owaspUtils.toBoolean( "f" ), false ); + assert.equal( owaspUtils.toBoolean( "T" ), true ); + assert.equal( owaspUtils.toBoolean( "F" ), false ); + assert.equal( owaspUtils.toBoolean( "1" ), true ); + assert.equal( owaspUtils.toBoolean( "-1" ), true ); + assert.equal( owaspUtils.toBoolean( "0" ), false ); + assert.equal( owaspUtils.toBoolean( "0.123" ), true ); + assert.equal( owaspUtils.toBoolean( "" ), false ); + } ); + + } ); +} ); + +describe( "OWASP-2", function() { + + describe( "Parsing utilities", function() { + + it( "URL validation", function() { + assert.equal( owaspUtils.validateURL( "http://127.0.0.1" ), true ); + assert.equal( owaspUtils.validateURL( "http://127.0.0.1/" ), true ); + assert.equal( owaspUtils.validateURL( "https://127.0.0.1:3344" ), true ); + assert.equal( owaspUtils.validateURL( "https://127.0.0.1:3344/" ), true ); + assert.equal( owaspUtils.validateURL( "ws://[::1]" ), true ); + assert.equal( owaspUtils.validateURL( "ws://[::1]/" ), true ); + assert.equal( owaspUtils.validateURL( "wss://[::1]:3344" ), true ); + assert.equal( owaspUtils.validateURL( "wss://[::1]:3344/" ), true ); + assert.equal( owaspUtils.validateURL( "http://some.domain.org" ), true ); + assert.equal( owaspUtils.validateURL( "http://some.domain.org/" ), true ); + assert.equal( owaspUtils.validateURL( "https://some.domain.org:3344" ), true ); + assert.equal( owaspUtils.validateURL( "https://some.domain.org:3344/" ), true ); + assert.equal( owaspUtils.validateURL( "hello ws://[::1]" ), false ); + assert.equal( owaspUtils.validateURL( "hello ws://[::1]/" ), false ); + assert.equal( owaspUtils.validateURL( "hello wss://[::1]:3344" ), false ); + assert.equal( owaspUtils.validateURL( "hello wss://[::1]:3344/" ), false ); + } ); + + it( "URL conversion", function() { + assert.equal( owaspUtils.toURL( "http://127.0.0.1" ).constructor.name, "URL" ); + assert.equal( owaspUtils.toURL( "https://127.0.0.1:3344" ).constructor.name, "URL" ); + assert.equal( owaspUtils.toURL( "https://127.0.0.1:3344/" ).constructor.name, "URL" ); + assert.equal( owaspUtils.toURL( "ws://[::1]" ).constructor.name, "URL" ); + assert.equal( owaspUtils.toURL( "ws://[::1]/" ).constructor.name, "URL" ); + assert.equal( owaspUtils.toURL( "wss://[::1]:3344" ).constructor.name, "URL" ); + assert.equal( owaspUtils.toURL( "wss://[::1]:3344/" ).constructor.name, "URL" ); + assert.equal( owaspUtils.toURL( "http://some.domain.org" ).constructor.name, "URL" ); + assert.equal( owaspUtils.toURL( "http://some.domain.org/" ).constructor.name, "URL" ); + assert.equal( + owaspUtils.toURL( "https://some.domain.org3344" ).constructor.name, "URL" ); + assert.equal( + owaspUtils.toURL( "https://some.domain.org:3344/" ).constructor.name, "URL" ); + assert.equal( + owaspUtils.toURL( "http://127.0.0.1" ).toString(), "http://127.0.0.1/" ); + assert.equal( + owaspUtils.toURL( "http://127.0.0.1/" ).toString(), "http://127.0.0.1/" ); + assert.equal( + owaspUtils.toURL( + "https://127.0.0.1:3344" ).toString(), "https://127.0.0.1:3344/" ); + assert.equal( + owaspUtils.toURL( + "https://127.0.0.1:3344/" ).toString(), "https://127.0.0.1:3344/" ); + assert.equal( owaspUtils.toURL( "ws://[::1]" ).toString(), "ws://[::1]/" ); + assert.equal( owaspUtils.toURL( "ws://[::1]/" ).toString(), "ws://[::1]/" ); + assert.equal( + owaspUtils.toURL( "wss://[::1]:3344" ).toString(), "wss://[::1]:3344/" ); + assert.equal( + owaspUtils.toURL( "wss://[::1]:3344/" ).toString(), "wss://[::1]:3344/" ); + assert.equal( + owaspUtils.toURL( + "http://some.domain.org" ).toString(), "http://some.domain.org/" ); + assert.equal( + owaspUtils.toURL( + "http://some.domain.org/" ).toString(), "http://some.domain.org/" ); + assert.equal( + owaspUtils.toURL( + "https://some.domain.org:3344" ).toString(), + "https://some.domain.org:3344/" ); + assert.equal( + owaspUtils.toURL( + "https://some.domain.org:3344/" ).toString(), + "https://some.domain.org:3344/" ); + assert.equal( + owaspUtils.toStringURL( "http://127.0.0.1" ), "http://127.0.0.1/" ); + assert.equal( + owaspUtils.toStringURL( "http://127.0.0.1/" ), "http://127.0.0.1/" ); + assert.equal( + owaspUtils.toStringURL( "https://127.0.0.1:3344" ), "https://127.0.0.1:3344/" ); + assert.equal( + owaspUtils.toStringURL( "https://127.0.0.1:3344/" ), "https://127.0.0.1:3344/" ); + assert.equal( owaspUtils.toStringURL( "ws://[::1]" ), "ws://[::1]/" ); + assert.equal( owaspUtils.toStringURL( "ws://[::1]/" ), "ws://[::1]/" ); + assert.equal( owaspUtils.toStringURL( "wss://[::1]:3344" ), "wss://[::1]:3344/" ); + assert.equal( owaspUtils.toStringURL( "wss://[::1]:3344/" ), "wss://[::1]:3344/" ); + assert.equal( + owaspUtils.toStringURL( + "http://some.domain.org" ), "http://some.domain.org/" ); + assert.equal( + owaspUtils.toStringURL( + "http://some.domain.org/" ), "http://some.domain.org/" ); + assert.equal( + owaspUtils.toStringURL( + "https://some.domain.org:3344" ), "https://some.domain.org:3344/" ); + assert.equal( + owaspUtils.toStringURL( + "https://some.domain.org:3344/" ), "https://some.domain.org:3344/" ); + } ); + + it( "Check URL is HTTP(S)", function() { + assert.equal( owaspUtils.isUrlHTTP( "http://127.0.0.1" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "http://localhost" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "http://[::1]" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "http://127.0.0.1:1234" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "http://localhost:1234" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "http://[::1]:1234" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "https://127.0.0.1" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "https://localhost" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "https://[::1]" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "https://127.0.0.1:1234" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "https://localhost:1234" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "https://[::1]:1234" ), true ); + assert.equal( owaspUtils.isUrlHTTP( "ws://127.0.0.1" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "ws://localhost" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "ws://[::1]" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "ws://127.0.0.1:1234" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "ws://localhost:1234" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "ws://[::1]:1234" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "wss://127.0.0.1" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "wss://localhost" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "wss://[::1]" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "wss://127.0.0.1:1234" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "wss://localhost:1234" ), false ); + assert.equal( owaspUtils.isUrlHTTP( "wss://[::1]:1234" ), false ); + } ); + + it( "Check URL is WS(S)", function() { + assert.equal( owaspUtils.isUrlWS( "http://127.0.0.1" ), false ); + assert.equal( owaspUtils.isUrlWS( "http://localhost" ), false ); + assert.equal( owaspUtils.isUrlWS( "http://[::1]" ), false ); + assert.equal( owaspUtils.isUrlWS( "http://127.0.0.1:1234" ), false ); + assert.equal( owaspUtils.isUrlWS( "http://localhost:1234" ), false ); + assert.equal( owaspUtils.isUrlWS( "http://[::1]:1234" ), false ); + assert.equal( owaspUtils.isUrlWS( "https://127.0.0.1" ), false ); + assert.equal( owaspUtils.isUrlWS( "https://localhost" ), false ); + assert.equal( owaspUtils.isUrlWS( "https://[::1]" ), false ); + assert.equal( owaspUtils.isUrlWS( "https://127.0.0.1:1234" ), false ); + assert.equal( owaspUtils.isUrlWS( "https://localhost:1234" ), false ); + assert.equal( owaspUtils.isUrlWS( "https://[::1]:1234" ), false ); + assert.equal( owaspUtils.isUrlWS( "ws://127.0.0.1" ), true ); + assert.equal( owaspUtils.isUrlWS( "ws://localhost" ), true ); + assert.equal( owaspUtils.isUrlWS( "ws://[::1]" ), true ); + assert.equal( owaspUtils.isUrlWS( "ws://127.0.0.1:1234" ), true ); + assert.equal( owaspUtils.isUrlWS( "ws://localhost:1234" ), true ); + assert.equal( owaspUtils.isUrlWS( "ws://[::1]:1234" ), true ); + assert.equal( owaspUtils.isUrlWS( "wss://127.0.0.1" ), true ); + assert.equal( owaspUtils.isUrlWS( "wss://localhost" ), true ); + assert.equal( owaspUtils.isUrlWS( "wss://[::1]" ), true ); + assert.equal( owaspUtils.isUrlWS( "wss://127.0.0.1:1234" ), true ); + assert.equal( owaspUtils.isUrlWS( "wss://localhost:1234" ), true ); + assert.equal( owaspUtils.isUrlWS( "wss://[::1]:1234" ), true ); + } ); + } ); +} ); + +describe( "OWASP-3", function() { + + describe( "Parsing utilities", function() { + + const strAddressValid0 = "0x7aa5E36AA15E93D10F4F26357C30F052DacDde5F"; + const strAddressValid1 = "7aa5E36AA15E93D10F4F26357C30F052DacDde5F"; + const strAddressInvalid0 = "0x7aa5E36AA15E93D10F4F26357C30F052DacDde5"; + const strAddressInvalid1 = "hello"; + const strAddressInvalid2 = ""; + + it( "Validate Ethereum address", function() { + assert.equal( owaspUtils.validateEthAddress( strAddressValid0 ), true ); + assert.equal( owaspUtils.validateEthAddress( strAddressValid1 ), true ); + assert.equal( owaspUtils.validateEthAddress( strAddressInvalid0 ), false ); + assert.equal( owaspUtils.validateEthAddress( strAddressInvalid1 ), false ); + assert.equal( owaspUtils.validateEthAddress( strAddressInvalid2 ), false ); + } ); + + it( "Parse Ethereum address", function() { + assert.equal( owaspUtils.toEthAddress( strAddressValid0 ), strAddressValid0 ); + assert.equal( owaspUtils.toEthAddress( strAddressValid1 ), strAddressValid0 ); + assert.equal( + owaspUtils.toEthAddress( + strAddressInvalid0, strAddressValid0 ), strAddressValid0 ); + assert.equal( + owaspUtils.toEthAddress( + strAddressInvalid0, "invalid value" ), "invalid value" ); + assert.equal( + owaspUtils.toEthAddress( + strAddressInvalid1, strAddressValid0 ), strAddressValid0 ); + assert.equal( + owaspUtils.toEthAddress( + strAddressInvalid1, "invalid value" ), "invalid value" ); + assert.equal( + owaspUtils.toEthAddress( + strAddressInvalid2, strAddressValid0 ), strAddressValid0 ); + assert.equal( + owaspUtils.toEthAddress( + strAddressInvalid2, "invalid value" ), "invalid value" ); + } ); + + const strPrivateKeyValid0 = + "23ABDBD3C61B5330AF61EBE8BEF582F4E5CC08E554053A718BDCE7813B9DC1FC"; + const strPrivateKeyValid1 = + "23ABDBD3C61B5330AF61EBE8BEF582F4E5CC08E554053A718BDCE7813B9DC1FC"; + const strPrivateKeyInvalid0 = + "23ABDBD3C61B5330AF61EBE8BEF582F4E5CC08E554053A718BDCE7813B9DC1F"; + const strPrivateKeyInvalid1 = "hello"; + const strPrivateKeyInvalid2 = ""; + + it( "Validate Ethereum private key", function() { + assert.equal( owaspUtils.validateEthPrivateKey( strPrivateKeyValid0 ), true ); + assert.equal( owaspUtils.validateEthPrivateKey( strPrivateKeyValid1 ), true ); + assert.equal( owaspUtils.validateEthPrivateKey( strPrivateKeyInvalid0 ), false ); + assert.equal( owaspUtils.validateEthPrivateKey( strPrivateKeyInvalid1 ), false ); + assert.equal( owaspUtils.validateEthPrivateKey( strPrivateKeyInvalid2 ), false ); + } ); + + it( "Parse Ethereum private key", function() { + assert.equal( + owaspUtils.toEthPrivateKey( strPrivateKeyValid0 ), strPrivateKeyValid0 ); + assert.equal( + owaspUtils.toEthPrivateKey( strPrivateKeyValid1 ), strPrivateKeyValid0 ); + assert.equal( + owaspUtils.toEthPrivateKey( + strPrivateKeyInvalid0, strPrivateKeyValid0 ), strPrivateKeyValid0 ); + assert.equal( + owaspUtils.toEthPrivateKey( + strPrivateKeyInvalid0, "invalid value" ), "invalid value" ); + assert.equal( + owaspUtils.toEthPrivateKey( + strPrivateKeyInvalid1, strPrivateKeyValid0 ), strPrivateKeyValid0 ); + assert.equal( + owaspUtils.toEthPrivateKey( + strPrivateKeyInvalid1, "invalid value" ), "invalid value" ); + assert.equal( + owaspUtils.toEthPrivateKey( + strPrivateKeyInvalid2, strPrivateKeyValid0 ), strPrivateKeyValid0 ); + assert.equal( + owaspUtils.toEthPrivateKey( + strPrivateKeyInvalid2, "invalid value" ), "invalid value" ); + } ); + + it( "Byte sequence utilities", function() { + assert.equal( owaspUtils.ensureStartsWith0x( "0x123" ), "0x123" ); + assert.equal( owaspUtils.ensureStartsWith0x( "123" ), "0x123" ); + assert.equal( owaspUtils.removeStarting0x( "0x123" ), "123" ); + assert.equal( owaspUtils.removeStarting0x( "123" ), "123" ); + } ); + + } ); +} ); + +describe( "OWASP-4", function() { + + describe( "Command line argument utilities", function() { + + it( "Basic verification", function() { + assert.equal( + typeof owaspUtils.verifyArgumentWithNonEmptyValue( + { name: "path", value: "/tmp/file.name.here" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsURL( + { name: "url", value: "http://127.0.0.1" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsURL( + { name: "url", value: "http://[::1]" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsURL( + { name: "url", value: "http://localhost" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsURL( + { name: "url", value: "http://127.0.0.1:1234" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsURL( + { name: "url", value: "http://[::1]:1234" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsURL( + { name: "url", value: "http://localhost:1234" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsInteger( + { name: "url", value: "123" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsInteger( + { name: "url", value: "0x123" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsIntegerIpPortNumber( + { name: "port", value: "1" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsIntegerIpPortNumber( + { name: "port", value: "123" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsIntegerIpPortNumber( + { name: "port", value: "1024" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsIntegerIpPortNumber( + { name: "port", value: "65535" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsArrayOfIntegers( + { name: "some_array", value: "[1]" } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsArrayOfIntegers( + { name: "some_array", value: "[1,2,3]" } ), "object" ); + } ); + + it( "Paths verification", function() { + assert.equal( + typeof owaspUtils.verifyArgumentIsPathToExistingFile( + { name: "url", value: __filename } ), "object" ); + assert.equal( + typeof owaspUtils.verifyArgumentIsPathToExistingFolder( + { name: "url", value: __dirname } ), "object" ); + } ); + + } ); +} ); + +describe( "OWASP-5", function() { + + describe( "Other utilities", function() { + + it( "IP from her", function() { + assert.equal( owaspUtils.ipFromHex( "0x0a0b0c0d" ), "10.11.12.13" ); + } ); + + it( "Clone object by root keys", function() { + const joIn = { "a": 1, "2": 2, "c": { "d": 3, "e": 4 } }; + const joOut = owaspUtils.cloneObjectByRootKeys( joIn ); + assert.equal( JSON.stringify( joIn ), JSON.stringify( joOut ) ); + } ); + + it( "ID from chain name", function() { + assert.equal( + owaspUtils.computeChainIdFromSChainName( "Hello World" ), + "0x592fa743889fc7" ); + } ); + + it( "Extract error message", function() { + const not_extracted = "error message was not extracted"; + assert.equal( + owaspUtils.extractErrorMessage( + null, not_extracted ), not_extracted ); + assert.equal( + owaspUtils.extractErrorMessage( + undefined, not_extracted ), not_extracted ); + assert.equal( + owaspUtils.extractErrorMessage( + 123, not_extracted ), not_extracted ); + assert.equal( + owaspUtils.extractErrorMessage( + "123", not_extracted ), not_extracted ); + assert.equal( + owaspUtils.extractErrorMessage( + "", not_extracted ), not_extracted ); + assert.equal( + owaspUtils.extractErrorMessage( + {}, not_extracted ), not_extracted ); + assert.equal( + owaspUtils.extractErrorMessage( + { "err": "something" }, not_extracted ), not_extracted ); + assert.equal( + owaspUtils.extractErrorMessage( + new Error( "Hello World" ), not_extracted ), "Hello World" ); + } ); + + } ); + +} ); + +describe( "OWASP-6", function() { + + describe( "Ethereum value of money utilities", function() { + + it( "Parse money unit name", function() { + assert.equal( owaspUtils.parseMoneyUnitName( "ethe" ), "ether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "ethr" ), "ether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "eth" ), "ether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "eter" ), "ether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "ete" ), "ether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "et" ), "ether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "eh" ), "ether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "er" ), "ether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "finne" ), "finney" ); + assert.equal( owaspUtils.parseMoneyUnitName( "finn" ), "finney" ); + assert.equal( owaspUtils.parseMoneyUnitName( "fin" ), "finney" ); + assert.equal( owaspUtils.parseMoneyUnitName( "fn" ), "finney" ); + assert.equal( owaspUtils.parseMoneyUnitName( "fi" ), "finney" ); + assert.equal( owaspUtils.parseMoneyUnitName( "szab" ), "szabo" ); + assert.equal( owaspUtils.parseMoneyUnitName( "szb" ), "szabo" ); + assert.equal( owaspUtils.parseMoneyUnitName( "sza" ), "szabo" ); + assert.equal( owaspUtils.parseMoneyUnitName( "sz" ), "szabo" ); + assert.equal( owaspUtils.parseMoneyUnitName( "shanno" ), "shannon" ); + assert.equal( owaspUtils.parseMoneyUnitName( "shannn" ), "shannon" ); + assert.equal( owaspUtils.parseMoneyUnitName( "shann" ), "shannon" ); + assert.equal( owaspUtils.parseMoneyUnitName( "shan" ), "shannon" ); + assert.equal( owaspUtils.parseMoneyUnitName( "sha" ), "shannon" ); + assert.equal( owaspUtils.parseMoneyUnitName( "shn" ), "shannon" ); + assert.equal( owaspUtils.parseMoneyUnitName( "sh" ), "shannon" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lovelac" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lovela" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lovel" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "love" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lovl" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lvl" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lvla" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lvlc" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lvc" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lv" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lo" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "lc" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "ll" ), "lovelace" ); + assert.equal( owaspUtils.parseMoneyUnitName( "babbag" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "babba" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "babbg" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "babb" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "bab" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "bag" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "bbb" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "bb" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "bg" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "ba" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "be" ), "babbage" ); + assert.equal( owaspUtils.parseMoneyUnitName( "we" ), "wei" ); + assert.equal( owaspUtils.parseMoneyUnitName( "wi" ), "wei" ); + assert.equal( owaspUtils.parseMoneyUnitName( "noether" ), "noether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "noeth" ), "noether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "kwei" ), "kwei" ); + assert.equal( owaspUtils.parseMoneyUnitName( "femtoether" ), "femtoether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "femto" ), "femtoether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "mwei" ), "mwei" ); + assert.equal( owaspUtils.parseMoneyUnitName( "picoether" ), "picoether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "pico" ), "picoether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "gwei" ), "gwei" ); + assert.equal( owaspUtils.parseMoneyUnitName( "nanoether" ), "nanoether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "nano" ), "nanoether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "microether" ), "microether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "micro" ), "microether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "milliether" ), "milliether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "milli" ), "milliether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "kether" ), "kether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "mether" ), "mether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "gether" ), "gether" ); + assert.equal( owaspUtils.parseMoneyUnitName( "tether" ), "tether" ); + } ); + + it( "Parse money value specification", function() { + assert.equal( owaspUtils.parseMoneySpecToWei( "1ether" ), "1000000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1ethe" ), "1000000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1ethr" ), "1000000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1eth" ), "1000000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1eter" ), "1000000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1ete" ), "1000000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1et" ), "1000000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1eh" ), "1000000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1er" ), "1000000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1finney" ), "1000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1finne" ), "1000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1finn" ), "1000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1fin" ), "1000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1fn" ), "1000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1fi" ), "1000000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1szab" ), "1000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1szb" ), "1000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1sza" ), "1000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1sz" ), "1000000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1shanno" ), "1000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1shannn" ), "1000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1shann" ), "1000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1shan" ), "1000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1sha" ), "1000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1shn" ), "1000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1sh" ), "1000000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lovelac" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lovela" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lovel" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1love" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lovl" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lvl" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lvla" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lvlc" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lvc" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lv" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lo" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1lc" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1ll" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1babbag" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1babba" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1babbg" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1babb" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1bab" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1bag" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1bbb" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1bb" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1bg" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1ba" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1be" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1we" ), "1" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1wi" ), "1" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1noether" ), "0" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1noeth" ), "0" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1kwei" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1femtoether" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1femto" ), "1000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1mwei" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1picoether" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1pico" ), "1000000" ); + assert.equal( owaspUtils.parseMoneySpecToWei( "1gwei" ), "1000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1nanoether" ), "1000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1nano" ), "1000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1microether" ), "1000000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1micro" ), "1000000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1milliether" ), "1000000000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1milli" ), "1000000000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1kether" ), "1000000000000000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1mether" ), "1000000000000000000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1gether" ), "1000000000000000000000000000" ); + assert.equal( + owaspUtils.parseMoneySpecToWei( "1tether" ), "1000000000000000000000000000000" ); + } ); + + } ); + +} ); + +describe( "CLI", function() { + + describe( "IMA Agent command line helpers", function() { + + it( "About", function() { + assert.equal( imaCLI.printAbout( true ), true ); + } ); + + it( "Parse and collect CLI argument", function() { + let joArg = imaCLI.parseCommandLineArgument( "--help" ); + assert.equal( joArg.name, "help" ); + assert.equal( joArg.value, "" ); + joArg = imaCLI.parseCommandLineArgument( "--test-url=http://127.0.0.1:3456" ); + assert.equal( joArg.name, "test-url" ); + assert.equal( joArg.value, "http://127.0.0.1:3456" ); + const isExitIfEmpty = false; + const isPrintValue = true; + const fnNameColorizer = null; + const fnValueColorizer = null; + assert.equal( + imaCLI.ensureHaveValue( + "test-url", + "http://127.0.0.1:3456", + isExitIfEmpty, + isPrintValue, + fnNameColorizer, + fnValueColorizer + ), + true ); + const joTestAccount = { + "privateKey": + owaspUtils.toEthPrivateKey( + "23ABDBD3C61B5330AF61EBE8BEF582F4E5CC08E554053A718BDCE7813B9DC1FC" ), + "address": function () { return owaspUtils.fnAddressImpl_( this ); } + }; + assert.equal( + imaCLI.ensureHaveCredentials( + imaState.chainProperties.sc.strChainName, + joTestAccount, + isExitIfEmpty, + isPrintValue ), + true ); + } ); + + } ); + + // TO-DO: imaCLI.findNodeIndex + + describe( "IMA Agent command line parser", function() { + + it( "Minimal command line parse", function() { + const joExternalHandlers = {}; + const argv = []; + assert.equal( imaCLI.parse( joExternalHandlers, argv ), 0 ); + } ); + + it( "Basic command line parse", function() { + const joExternalHandlers = {}; + const argv = [ + "--verbose=9", + "--s2s-disable", + "--url-main-net=" + imaState.chainProperties.mn.strURL, + "--url-s-chain=" + imaState.chainProperties.sc.strURL, + "--id-main-net=" + imaState.chainProperties.mn.strChainName, + "--id-s-chain=" + imaState.chainProperties.sc.strChainName, + "--id-origin-chain=" + imaState.strChainNameOriginChain, + "--cid-main-net=" + imaState.chainProperties.mn.chainId, + "--cid-s-chain=" + imaState.chainProperties.sc.chainId, + "--address-main-net=" + + ( imaState.chainProperties.mn.joAccount.address() || + "0x7aa5e36aa15e93d10f4f26357c30f052dacdde5f" ), + "--address-s-chain=" + + ( imaState.chainProperties.sc.joAccount.address() || + "0x66c5a87f4a49DD75e970055A265E8dd5C3F8f852" ), + "--key-main-net=" + + ( imaState.chainProperties.mn.joAccount.privateKey || + "23ABDBD3C61B5330AF61EBE8BEF582F4E5CC08E554053A718BDCE7813B9DC1FC" ), + "--key-s-chain=" + ( imaState.chainProperties.sc.joAccount.privateKey || + "80ebc2e00b8f13c5e2622b5694ab63ee80f7c5399554d2a12feeb0212eb8c69e" ), + //"--abi-skale-manager=" + imaState.strPathAbiJsonSkaleManager, + "--abi-main-net=" + imaState.chainProperties.mn.strPathAbiJson, + "--abi-s-chain=" + imaState.chainProperties.sc.strPathAbiJson, + // --erc721-main-net --erc721-s-chain --addr-erc721-s-chain + // --erc20-main-net --erc20-s-chain --addr-erc20-s-chain + // --erc1155-main-net --erc1155-s-chain --addr-erc1155-s-chain + "--sleep-between-tx=5000", + "--wait-next-block=true", + // --value... + "--gas-price-multiplier-mn=2", + "--gas-price-multiplier-sc=2", + "--gas-price-multiplier=2", + // --no-wait-s-chain --max-wait-attempts + "--skip-dry-run", // --skip-dry-run --ignore-dry-run --dry-run + "--m2s-transfer-block-size=4", + "--s2m-transfer-block-size=4", + "--s2s-transfer-block-size=4", + "--transfer-block-size=4", + "--m2s-max-transactions=0", + "--s2m-max-transactions=0", + "--s2s-max-transactions=0", + "--max-transactions=0", + "--m2s-await-blocks=0", + "--s2m-await-blocks=0", + "--s2s-await-blocks=0", + "--await-blocks=0", + "--m2s-await-time=0", + "--s2m-await-time=0", + "--s2s-await-time=0", + "--await-time=0", + "--period=300", + "--node-number=0", + "--nodes-count=1", + "--time-framing=0", + "--time-gap=10", + "--no-pwa" + // --log-size --log-files --log + // --sign-messages --bls-glue --hash-g1 --bls-verify + ]; + assert.equal( imaCLI.parse( joExternalHandlers, argv ), 0 ); + } ); + + } ); + +} ); + +describe( "Agent Utils Module-1", function() { + + describe( "String helpers", function() { + + it( "Text replacement", function() { + assert.equal( imaUtils.replaceAll( "abc123abcdef456abc", "abc", "" ), "123def456" ); + } ); + + it( "Random file name", function() { + const strPathTmpFolder = os.tmpdir(); + const strPathTmpFile = + path.join( strPathTmpFolder, imaUtils.getRandomFileName() + ".txt" ); + assert.equal( strPathTmpFile ? true : false, true ); + } ); + + it( "Compose S-Chain URL", function() { + assert.equal( + imaUtils.composeSChainNodeUrl( + { ip: "127.0.0.1", httpRpcPort: 3456 } ), "http://127.0.0.1:3456" ); + assert.equal( + imaUtils.composeSChainNodeUrl( + { ip: "127.0.0.1", httpsRpcPort: 3456 } ), "https://127.0.0.1:3456" ); + assert.equal( + imaUtils.composeSChainNodeUrl( + { ip: "127.0.0.1", wsRpcPort: 3456 } ), "ws://127.0.0.1:3456" ); + assert.equal( + imaUtils.composeSChainNodeUrl( + { ip: "127.0.0.1", wssRpcPort: 3456 } ), "wss://127.0.0.1:3456" ); + assert.equal( + imaUtils.composeSChainNodeUrl( + { ip6: "::1", httpRpcPort6: 3456 } ), "http://[::1]:3456" ); + assert.equal( + imaUtils.composeSChainNodeUrl( + { ip6: "::1", httpsRpcPort6: 3456 } ), "https://[::1]:3456" ); + assert.equal( + imaUtils.composeSChainNodeUrl( + { ip6: "::1", wsRpcPort6: 3456 } ), "ws://[::1]:3456" ); + assert.equal( + imaUtils.composeSChainNodeUrl( + { ip6: "::1", wssRpcPort6: 3456 } ), "wss://[::1]:3456" ); + } ); + + it( "Compose IMA Agent URL", function() { + // HTTP_JSON = 3 + // IMA_AGENT_JSON = 10 + // so... distance is 10 - 3 = 7 + // as result, 14999 + 7 = 15006 + assert.equal( + imaUtils.composeImaAgentNodeUrl( + { ip: "127.0.0.1", httpRpcPort: 14999 }, true ), "http://127.0.0.1:15006" ); + assert.equal( + imaUtils.composeImaAgentNodeUrl( + { ip: "127.0.0.1", httpRpcPort: 14999 }, false ), "http://127.0.0.1:15006" ); + } ); + + } ); + +} ); + +describe( "Agent Utils Module-2", function() { + + describe( "Byte array manipulation helpers", function() { + + it( "HEX encode/decode raw", function() { + const strSrc = "5465737420737472696e6720313233"; + const arrBytes = imaUtils.hexToBytes( strSrc, false ); + const strDst = imaUtils.bytesToHex( arrBytes, false ); + assert.equal( strSrc, strDst ); + } ); + + it( "HEX encode/decode 0x-prefixed", function() { + const strSrc = "0x5465737420737472696e6720313233"; + const arrBytes = imaUtils.hexToBytes( strSrc, false ); + const strDst = imaUtils.bytesToHex( arrBytes, false ); + assert.equal( strSrc, "0x" + strDst ); + } ); + + it( "HEX encode/decode with inversive order raw", function() { + const strSrc = "5465737420737472696e6720313233"; + const arrBytes = imaUtils.hexToBytes( strSrc, true ); + const strDst = imaUtils.bytesToHex( arrBytes, true ); + assert.equal( strSrc, strDst ); + } ); + + it( "HEX encode/decode with inversive order 0x-prefixed", function() { + const strSrc = "0x5465737420737472696e6720313233"; + const arrBytes = imaUtils.hexToBytes( strSrc, true ); + const strDst = imaUtils.bytesToHex( arrBytes, true ); + assert.equal( strSrc, "0x" + strDst ); + } ); + + it( "Array padding with zeroes at left", function() { + const strSrc = "123"; + const arrBytes = + imaUtils.bytesAlignLeftWithZeroes( + imaUtils.hexToBytes( strSrc, false ), 4 ); + const strDst = imaUtils.bytesToHex( arrBytes, false ); + assert.equal( strDst, "00000123" ); + } ); + + it( "Array padding with zeroes at right", function() { + const strSrc = "123"; + const arrBytes = + imaUtils.bytesAlignRightWithZeroes( + imaUtils.hexToBytes( strSrc, false ), 4 ); + const strDst = imaUtils.bytesToHex( arrBytes, false ); + assert.equal( strDst, "01230000" ); + } ); + + it( "Uint8 arrays concatenation", function() { + const strSrcLeft = "0xbaad", strSrcRight = "0xf00d"; + const arrBytesLeft = imaUtils.hexToBytes( strSrcLeft, false ); + const arrBytesRight = imaUtils.hexToBytes( strSrcRight, false ); + const arrBytes = imaUtils.concatUint8Arrays( arrBytesLeft, arrBytesRight ); + const strDst = imaUtils.bytesToHex( arrBytes, false ); + assert.equal( strDst, "baadf00d" ); + } ); + + it( "Byte array concatenation", function() { + const strSrcLeft = "0xbaad", strSrcRight = "0xf00d"; + const arrBytesLeft = imaUtils.hexToBytes( strSrcLeft, false ); + const arrBytesRight = imaUtils.hexToBytes( strSrcRight, false ); + const arrBytes = imaUtils.bytesConcat( arrBytesLeft, arrBytesRight ); + const strDst = imaUtils.bytesToHex( arrBytes, false ); + assert.equal( strDst, "baadf00d" ); + } ); + + it( "Single Byte concatenation", function() { + const strSrcLeft = "0xbaadf0", nSrcRight = 0x0d; + const arrBytesLeft = imaUtils.hexToBytes( strSrcLeft, false ); + const arrBytes = imaUtils.concatByte( arrBytesLeft, nSrcRight ); + const strDst = imaUtils.bytesToHex( arrBytes, false ); + assert.equal( strDst, "baadf00d" ); + } ); + + } ); + +} ); + +describe( "Agent Utils Module-3", function() { + + describe( "Path/file/JSON helpers", function() { + + it( "Home directory and path normalization", function() { + const strPathHomeFolder = imaUtils.normalizePath( "~" ); + const strPathSrc = "~/some/file/path/here"; + const strPathDst = strPathHomeFolder + "/some/file/path/here"; + assert.equal( imaUtils.normalizePath( strPathSrc ), strPathDst ); + } ); + + it( "File existence and text loading/saving", function() { + const strPathTmpFolder = os.tmpdir(); + const strPathTmpFile = + path.join( strPathTmpFolder, imaUtils.getRandomFileName() + ".txt" ); + try { fs.unlinkSync( strPathTmpFile ); } catch ( err ) { }; + assert.equal( imaUtils.fileExists( strPathTmpFile ), false ); + const strContentSaved = "Text file content"; + assert.equal( imaUtils.fileSave( strPathTmpFile, strContentSaved ), true ); + assert.equal( imaUtils.fileExists( strPathTmpFile ), true ); + const strContentLoaded = imaUtils.fileLoad( + strPathTmpFile, "file \"" + strPathTmpFile + "\"was not loaded" ); + assert.equal( strContentLoaded, strContentSaved ); + try { fs.unlinkSync( strPathTmpFile ); } catch ( err ) { }; + } ); + + it( "File existence and JSON loading/saving", function() { + const strPathTmpFolder = os.tmpdir(); + const strPathTmpFile = + path.join( strPathTmpFolder, imaUtils.getRandomFileName() + ".json" ); + try { fs.unlinkSync( strPathTmpFile ); } catch ( err ) { }; + assert.equal( imaUtils.fileExists( strPathTmpFile ), false ); + const joContentSaved = { a: 123, b: 456 }; + assert.equal( imaUtils.jsonFileSave( strPathTmpFile, joContentSaved ), true ); + assert.equal( imaUtils.fileExists( strPathTmpFile ), true ); + const joContentLoaded = + imaUtils.jsonFileLoad( + strPathTmpFile, + { error: "file \"" + strPathTmpFile + "\"was not loaded" } ); + assert.equal( + JSON.stringify( joContentSaved ), JSON.stringify( joContentLoaded ) ); + try { fs.unlinkSync( strPathTmpFile ); } catch ( err ) { }; + } ); + + } ); + + describe( "ABI JSON Helpers", function() { + + it( "Find ABI entries", function() { + const strName = imaState.chainProperties.sc.strChainName; + const strFile = imaState.chainProperties.sc.strPathAbiJson; + const joABI = + imaUtils.jsonFileLoad( + strFile, + { error: "file \"" + strFile + "\"was not loaded" } ); + const strKey = "token_manager_linker_address"; + const arrKeys = [ + "token_manager_linker_address", + "token_manager_linker_abi", + "eth_erc20_address", + "eth_erc20_abi", + "token_manager_eth_address", + "token_manager_eth_abi", + "token_manager_erc20_address", + "token_manager_erc20_abi", + "token_manager_erc721_address", + "token_manager_erc721_abi", + "token_manager_erc1155_address", + "token_manager_erc1155_abi", + "message_proxy_chain_address", + "message_proxy_chain_abi" + ]; + const isExitOnError = false; + assert.equal( + imaUtils.checkKeyExistInABI( + strName, strFile, joABI, strKey, isExitOnError ), true ); + assert.equal( + imaUtils.checkKeysExistInABI( + strName, strFile, joABI, arrKeys, isExitOnError ), true ); + } ); + + it( "Discover coin name", function() { + const strFile = imaState.chainProperties.sc.strPathAbiJson; + const joABI = + imaUtils.jsonFileLoad( + strFile, + { error: "file \"" + strFile + "\"was not loaded" } ); + const strCoinName = + imaUtils.discoverCoinNameInJSON( joABI ); + assert.equal( strCoinName.length > 0, true ); + } ); + + } ); + +} ); diff --git a/test/agent/agent.py b/test/agent/agent.py new file mode 100644 index 00000000..7c115248 --- /dev/null +++ b/test/agent/agent.py @@ -0,0 +1,453 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +import errno +from time import time, sleep +from subprocess import Popen +from logging import debug +import json +import os + +from tools.blockchain import BlockChain +from tools.utils import execute + + +class Agent: + config = None + started = False + agent_service = None + blockchain = None + + def __init__(self, config): + self.config = config + self.blockchain = BlockChain(config) + + def register(self): + self._execute_command( + 'register', + { + 'colors': None + } + ) + + def start(self): + if self.agent_service is None: + self.agent_service = Popen(self._construct_command('loop')) + debug(f'Agent process #{self.agent_service.pid}') + + def stop(self): + if self.agent_service is not None: + self.agent_service.terminate() + self.agent_service = None + + def transfer_eth_from_mainnet_to_schain(self, from_key, to_key, amount_wei, timeout=0): + destination_address = self.blockchain.key_to_address(from_key) + balance, initial_balance = None, None + start = time() + if timeout > 0: + balance = self.blockchain.get_balance_on_schain(destination_address) + initial_balance = balance + + self._execute_command( + 'm2s-payment', + { + **self._wei_to_bigger(amount_wei), + 'key-main-net': from_key, + 'colors': None + } + ) + + if timeout > 0: + while not balance == initial_balance + amount_wei: + balance = self.blockchain.get_balance_on_schain(destination_address) + + if time() > start + timeout: + return + else: + sleep( 1 ) + + def transfer_eth_from_schain_to_mainnet(self, from_key, to_key, amount_wei, timeout=0): + destination_address = self.blockchain.key_to_address(from_key) + initial_approved, approved, balance, initial_balance = None, None, None, None + start = time() + if timeout > 0: + approved = self.blockchain.get_approved_amount(destination_address) + initial_approved = approved + + self._execute_command( + 's2m-payment', + { + **self._wei_to_bigger(amount_wei), + 'key-s-chain': from_key, + 'key-main-net': to_key, + 'colors': None + } + ) + + if timeout > 0: + while not approved >= initial_approved + amount_wei - 6 * 10 ** 16: + approved = self.blockchain.get_approved_amount(destination_address) + debug(f'Approved: {approved}') + + if time() > start + timeout: + return + else: + sleep( 1 ) + balance = self.blockchain.get_balance_on_mainnet(destination_address) + initial_balance = balance + start = time() + debug(f'Initial balance: {initial_balance}') + + self._execute_command( + 's2m-receive', + { + 'key-main-net': from_key, + 'colors': None + } + ) + + if timeout > 0: + approximate_gas_spends = 3 * 10 ** 15 + while not balance > initial_balance + approved - approximate_gas_spends: + balance = self.blockchain.get_balance_on_mainnet(destination_address) + debug(f'Balance: {balance}') + + if time() > start + timeout: + return + else: + sleep( 1 ) + + def transfer_erc20_from_mainnet_to_schain(self, token_contract, from_key, to_key, amount, amount_wei, timeout=0): + config_json = {'token_address': token_contract.address, 'token_abi': token_contract.abi} + erc20_config_filename = self.config.test_working_dir + '/erc20.json' + self._create_path(erc20_config_filename) + with open(erc20_config_filename, 'w') as erc20_file: + json.dump(config_json, erc20_file) + + self._execute_command( + 'm2s-payment', + { + **self._wei_to_bigger(amount_wei), + 'amount': amount, + 'key-main-net': from_key, + 'key-s-chain': to_key, + 'erc20-main-net': erc20_config_filename, + 'colors': None + } + ) + + start = time() + while time() < start + timeout if timeout > 0 else True: + try: + self.blockchain.get_erc20_on_schain("Mainnet", token_contract.address) + return + except ValueError: + debug('Wait for erc20 deployment') + sleep( 1 ) + + def transfer_erc721_from_mainnet_to_schain(self, token_contract, from_key, to_key, token_id, amount_wei, timeout=0): + config_json = {'token_address': token_contract.address, 'token_abi': token_contract.abi} + erc721_config_filename = self.config.test_working_dir + '/erc721.json' + self._create_path(erc721_config_filename) + with open(erc721_config_filename, 'w') as erc721_file: + json.dump(config_json, erc721_file) + sleep( 5 ) + + self._execute_command( + 'm2s-payment', + { + **self._wei_to_bigger(amount_wei), + 'tid': token_id, + 'key-main-net': from_key, + 'key-s-chain': to_key, + 'erc721-main-net': erc721_config_filename, + 'colors': None + } + ) + + start = time() + while time() < start + timeout if timeout > 0 else True: + try: + self.blockchain.get_erc721_on_schain("Mainnet", token_contract.address) + return + except ValueError: + debug('Wait for erc721 deployment') + sleep( 1 ) + + def transfer_erc1155_from_mainnet_to_schain(self, token_contract, from_key, to_key, token_id, token_amount, amount_wei, timeout=0): + config_json = {'token_address': token_contract.address, 'token_abi': token_contract.abi} + erc1155_config_filename = self.config.test_working_dir + '/erc1155.json' + self._create_path(erc1155_config_filename) + with open(erc1155_config_filename, 'w') as erc1155_file: + json.dump(config_json, erc1155_file) + sleep( 5 ) + + self._execute_command( + 'm2s-payment', + { + **self._wei_to_bigger(amount_wei), + 'tid': token_id, + 'amount': token_amount, + 'key-main-net': from_key, + 'key-s-chain': to_key, + 'erc1155-main-net': erc1155_config_filename, + 'colors': None + } + ) + + start = time() + while time() < start + timeout if timeout > 0 else True: + try: + self.blockchain.get_erc1155_on_schain("Mainnet", token_contract.address) + return + except ValueError: + debug('Wait for erc1155 deployment') + sleep( 1 ) + + def transfer_erc1155_batch_from_mainnet_to_schain(self, token_contract, from_key, to_key, token_ids, token_amounts, amount_wei, timeout=0): + config_json = {'token_address': token_contract.address, 'token_abi': token_contract.abi} + erc1155_config_filename = self.config.test_working_dir + '/erc1155.json' + self._create_path(erc1155_config_filename) + with open(erc1155_config_filename, 'w') as erc1155_file: + json.dump(config_json, erc1155_file) + sleep( 5 ) + + self._execute_command( + 'm2s-payment', + { + **self._wei_to_bigger(amount_wei), + 'tids': str(token_ids).replace(' ', ''), + 'amounts': str(token_amounts).replace(' ', ''), + 'key-main-net': from_key, + 'key-s-chain': to_key, + 'erc1155-main-net': erc1155_config_filename, + 'colors': None + } + ) + + start = time() + while time() < start + timeout if timeout > 0 else True: + try: + self.blockchain.get_erc1155_on_schain("Mainnet", token_contract.address) + return + except ValueError: + debug('Wait for erc1155 deployment') + sleep( 1 ) + + def transfer_erc20_from_schain_to_mainnet(self, token_contract, token_contract_on_mainnet, from_key, to_key, amount, amount_wei, timeout=0): + config_schain_json = {'token_address': token_contract.address, 'token_abi': token_contract.abi} + config_mainnet_json = {'token_address': token_contract_on_mainnet.address, 'token_abi': token_contract_on_mainnet.abi} + erc20_clone_config_filename = self.config.test_working_dir + '/erc20_clone.json' + erc20_config_filename = self.config.test_working_dir + '/erc20.json' + self._create_path(erc20_clone_config_filename) + self._create_path(erc20_config_filename) + with open(erc20_clone_config_filename, 'w') as erc20_file: + json.dump(config_schain_json, erc20_file) + with open(erc20_config_filename, 'w') as erc20_file: + json.dump(config_mainnet_json, erc20_file) + + destination_address = self.blockchain.key_to_address(from_key) + erc20 = token_contract_on_mainnet + balance = erc20.functions.balanceOf(destination_address).call() + + tx_count = self.blockchain.get_transactions_count_on_mainnet(destination_address) + + self._execute_command( + 's2m-payment', + { + **self._wei_to_bigger(amount_wei), + 'amount': amount, + 'key-main-net': to_key, + 'key-s-chain': from_key, + 'erc20-main-net': erc20_config_filename, + 'erc20-s-chain': erc20_clone_config_filename, + 'colors': None + } + ) + + start = time() + while (time() < start + timeout if timeout > 0 else True) and \ + balance == erc20.functions.balanceOf(destination_address).call(): + debug('Wait for erc20 payment') + sleep( 1 ) + + def transfer_erc721_from_schain_to_mainnet(self, token_contract, token_contract_on_mainnet, from_key, to_key, token_id, amount_wei, timeout=0): + config_schain_json = {'token_address': token_contract.address, 'token_abi': token_contract.abi} + config_mainnet_json = {'token_address': token_contract_on_mainnet.address, 'token_abi': token_contract_on_mainnet.abi} + erc721_clone_config_filename = self.config.test_working_dir + '/erc721_clone.json' + erc721_config_filename = self.config.test_working_dir + '/erc721.json' + self._create_path(erc721_clone_config_filename) + self._create_path(erc721_config_filename) + with open(erc721_clone_config_filename, 'w') as erc721_file: + json.dump(config_schain_json, erc721_file) + with open(erc721_config_filename, 'w') as erc721_file: + json.dump(config_mainnet_json, erc721_file) + + erc721 = token_contract_on_mainnet + destination_address = erc721.functions.ownerOf(token_id).call() + tx_count = self.blockchain.get_transactions_count_on_mainnet(destination_address) + sleep( 10 ) + self._execute_command( + 's2m-payment', + { + **self._wei_to_bigger(amount_wei), + 'tid': token_id, + 'key-main-net': to_key, + 'key-s-chain': from_key, + 'erc721-main-net': erc721_config_filename, + 'erc721-s-chain': erc721_clone_config_filename, + 'colors': None + } + ) + + start = time() + while (time() < start + timeout if timeout > 0 else True) and \ + destination_address == erc721.functions.ownerOf(token_id).call(): + debug('Wait for erc721 payment') + sleep( 1 ) + + def transfer_erc1155_from_schain_to_mainnet(self, token_contract, token_contract_on_mainnet, from_key, to_key, token_id, token_amount, amount_wei, timeout=0): + config_schain_json = {'token_address': token_contract.address, 'token_abi': token_contract.abi} + config_mainnet_json = {'token_address': token_contract_on_mainnet.address, 'token_abi': token_contract_on_mainnet.abi} + erc1155_clone_config_filename = self.config.test_working_dir + '/erc1155_clone.json' + erc1155_config_filename = self.config.test_working_dir + '/erc1155.json' + self._create_path(erc1155_clone_config_filename) + self._create_path(erc1155_config_filename) + with open(erc1155_clone_config_filename, 'w') as erc1155_file: + json.dump(config_schain_json, erc1155_file) + with open(erc1155_config_filename, 'w') as erc1155_file: + json.dump(config_mainnet_json, erc1155_file) + + erc1155 = token_contract_on_mainnet + destination_address = self.blockchain.key_to_address(from_key) + sleep( 10 ) + self._execute_command( + 's2m-payment', + { + **self._wei_to_bigger(amount_wei), + 'tid': token_id, + 'amount': token_amount, + 'key-main-net': to_key, + 'key-s-chain': from_key, + 'erc1155-main-net': erc1155_config_filename, + 'erc1155-s-chain': erc1155_clone_config_filename, + 'colors': None + } + ) + + start = time() + while (time() < start + timeout if timeout > 0 else True) and \ + token_amount != erc1155.functions.balanceOf(destination_address, token_id).call(): + debug('Wait for erc1155 payment') + sleep( 1 ) + + def transfer_erc1155_batch_from_schain_to_mainnet(self, token_contract, token_contract_on_mainnet, from_key, to_key, token_ids, token_amounts, amount_wei, timeout=0): + config_schain_json = {'token_address': token_contract.address, 'token_abi': token_contract.abi} + config_mainnet_json = {'token_address': token_contract_on_mainnet.address, 'token_abi': token_contract_on_mainnet.abi} + erc1155_clone_config_filename = self.config.test_working_dir + '/erc1155_clone.json' + erc1155_config_filename = self.config.test_working_dir + '/erc1155.json' + self._create_path(erc1155_clone_config_filename) + self._create_path(erc1155_config_filename) + with open(erc1155_clone_config_filename, 'w') as erc1155_file: + json.dump(config_schain_json, erc1155_file) + with open(erc1155_config_filename, 'w') as erc1155_file: + json.dump(config_mainnet_json, erc1155_file) + + erc1155 = token_contract_on_mainnet + destination_address = self.blockchain.key_to_address(from_key) + sleep( 10 ) + self._execute_command( + 's2m-payment', + { + **self._wei_to_bigger(amount_wei), + 'tids': str(token_ids).replace(' ', ''), + 'amounts': str(token_amounts).replace(' ', ''), + 'key-main-net': to_key, + 'key-s-chain': from_key, + 'erc1155-main-net': erc1155_config_filename, + 'erc1155-s-chain': erc1155_clone_config_filename, + 'colors': None + } + ) + + start = time() + while (time() < start + timeout if timeout > 0 else True) and \ + token_amounts != erc1155.functions.balanceOfBatch([destination_address]*len(token_ids), token_ids).call(): + debug('Wait for erc1155 payment') + sleep( 1 ) + + # private + + def _execute_command(self, command, flags=None): + if flags is None: + flags = {} + execute(self._format_command(command, flags)) + + def _construct_command(self, command, flags=None): + if flags is None: + flags = {} + flags = {**self._get_default_flags(), command: None, **flags} + + return ['node', + f'{self.config.agent_src}/build/main.js'] + \ + [f'--{key}' + (f'={str(value)}' if value is not None else '') for key, value in flags.items() ] + + def _format_command(self, command, flags=None): + if flags is None: + flags = {} + return ' '.join(self._construct_command(command, flags)) + + def _get_default_flags(self): + return { + 'verbose': 9, + 's2s-disable': None, + 'url-main-net': self.config.mainnet_rpc_url, + 'url-s-chain': self.config.schain_rpc_url, + 'id-main-net': 'Mainnet', + 'id-s-chain': self.config.schain_name, + 'abi-main-net': self.config.abi_mainnet, + 'abi-s-chain': self.config.abi_schain, + 'key-main-net': self.config.mainnet_key, + 'key-s-chain': self.config.schain_key, + 'no-wait-s-chain': None, + 'no-pwa': None, + 'gas-price-multiplier': '2.0', + 'gas-multiplier': '2.0', + 'colors': None, + 'no-expose': None, + 'no-expose-pwa': None, + 'no-expose-security-info': None, + 'no-gathered': None, + 'dynamic-log-in-transfer': None, + 'accumulated-log-in-bls-signer': None + } + + def _wei_to_bigger(self, amount): + new_amount, unit = self.blockchain.wei_to_bigger(amount) + return {unit: new_amount} + + @staticmethod + def _create_path(filename): + if not os.path.exists(os.path.dirname(filename)): + try: + os.makedirs(os.path.dirname(filename)) + except OSError as exc: # Guard against race condition + if exc.errno != errno.EEXIST: + raise diff --git a/test/agent/agent_environment.py b/test/agent/agent_environment.py new file mode 100644 index 00000000..3921d6cf --- /dev/null +++ b/test/agent/agent_environment.py @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from tools.environment import Environment +from os import chdir +from tools.utils import execute + +class AgentEnvironment(Environment): + config = None + + def __init__(self, config): + self.config = config + + def prepare(self): + chdir(self.config.proxy_root) + execute('rm -rf node_modules') + execute('yarn install') + + chdir(self.config.agent_src) + execute('rm -rf node_modules') + execute('yarn install') + execute('yarn rebuild') diff --git a/test/config.example.json b/test/config.example.json new file mode 100644 index 00000000..bf76d5b6 --- /dev/null +++ b/test/config.example.json @@ -0,0 +1,10 @@ +{ + "NETWORK_FOR_ETHEREUM": "mainnet", + "PRIVATE_KEY_FOR_ETHEREUM": "{private_key}", + "URL_W3_ETHEREUM": "http://localhost:8545", + "NETWORK_FOR_SCHAIN": "schain", + "PRIVATE_KEY_FOR_SCHAIN": "{private_key}", + "URL_W3_S_CHAIN": "http://localhost:8545", + "CHAIN_NAME_SCHAIN": "SKALE", + "user_key": "{private_key}" +} \ No newline at end of file diff --git a/test/package.json b/test/package.json new file mode 100644 index 00000000..5abec3c6 --- /dev/null +++ b/test/package.json @@ -0,0 +1,21 @@ +{ + "name": "skale-ima-tests", + "license": "AGPL-3.0", + "author": "SKALE Labs and contributors", + "scripts": { + "lint-check": "eslint ./*.*js", + "lint-fix": "eslint ./*.*js --fix", + "test": "npx mocha ./agent-test.mjs" + }, + "dependencies": {}, + "devDependencies": { + "eslint": "^6.8.0", + "eslint-config-standard": "^14.1.1", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.1", + "istanbul": "^0.4.5", + "mocha": "^8.2.1" + } +} diff --git a/test/proxy/deployer.py b/test/proxy/deployer.py new file mode 100644 index 00000000..b99b5d82 --- /dev/null +++ b/test/proxy/deployer.py @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from tools.utils import execute +from os import chdir + +class Deployer: + def __init__(self, config): + self.config = config + + def deploy(self): + chdir(self.config.proxy_root) + self._prepare_env_file() + execute('yarn deploy-skale-manager-components') + execute('yarn deploy-to-both-chains') + + def deploy_mainnet(self): + chdir(self.config.proxy_root) + self._prepare_env_file() + execute('yarn deploy-skale-manager-components') + execute('yarn deploy-to-mainnet') + + def deploy_schain(self, schain_name): + chdir(self.config.proxy_root) + self._prepare_env_file(schain_name) + execute('yarn deploy-to-schain') + + def deploy_second_schain(self): + self.deploy() + self.deploy_schain(self.config.schain_name_2) + + + # private + + def _prepare_env_file(self, schain_name=''): + if schain_name == '' : schain_name = self.config.schain_name + env_file = [f'NETWORK_FOR_ETHEREUM="{self.config.network_for_mainnet}"', + f'NETWORK_FOR_SCHAIN="{self.config.network_for_schain}"', + f'PRIVATE_KEY_FOR_ETHEREUM="{self.config.mainnet_key}"', + f'URL_W3_ETHEREUM="{self.config.mainnet_rpc_url}"', + f'PRIVATE_KEY_FOR_SCHAIN="{self.config.schain_key}"', + f'URL_W3_S_CHAIN="{self.config.schain_rpc_url}"', + f'CHAIN_NAME_SCHAIN="{schain_name}"', + 'NO_SIGNATURES=true'] + + with open('.env', 'w') as dot_env: + dot_env.write('\n'.join(env_file)) + diff --git a/test/proxy/proxy_environment.py b/test/proxy/proxy_environment.py new file mode 100644 index 00000000..a25cd2ab --- /dev/null +++ b/test/proxy/proxy_environment.py @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from os import chdir +from tools.environment import Environment +from tools.utils import execute + +class ProxyEnvironment(Environment): + config = None + + def __init__(self, config): + self.config = config + + def prepare(self): + chdir(self.config.proxy_root) + execute('yarn install') diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 00000000..b2de950e --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1 @@ +web3==5.24.0 \ No newline at end of file diff --git a/test/resources/ERC1155BurnableMintable.json b/test/resources/ERC1155BurnableMintable.json new file mode 100644 index 00000000..b681692b --- /dev/null +++ b/test/resources/ERC1155BurnableMintable.json @@ -0,0 +1,676 @@ +{ + "contractName": "VASYA1155", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "uri", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MINTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "burnBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mintBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x60806040523480156200001157600080fd5b5060405162002e2438038062002e24833981810160405260208110156200003757600080fd5b81019080805160405193929190846401000000008211156200005857600080fd5b9083019060208201858111156200006e57600080fd5b82516401000000008111828201881017156200008957600080fd5b82525081516020918201929091019080838360005b83811015620000b85781810151838201526020016200009e565b50505050905090810190601f168015620000e65780820380516001836020036101000a031916815260200191505b50604052508291506200010290506301ffc9a760e01b62000178565b6200010d8162000200565b6200011f636cdb3d1360e11b62000178565b620001316303a24d0760e21b62000178565b506200014d60008051602062002e048339815191528062000219565b6200017160008051602062002e048339815191526200016b6200026b565b6200026f565b5062000411565b6001600160e01b03198082161415620001d8576040805162461bcd60e51b815260206004820152601c60248201527f4552433136353a20696e76616c696420696e7465726661636520696400000000604482015290519081900360640190fd5b6001600160e01b0319166000908152600160208190526040909120805460ff19169091179055565b80516200021590600490602084019062000375565b5050565b600082815260208190526040808220600201549051839285917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9190a460009182526020829052604090912060020155565b3390565b6200021582826000828152602081815260409091206200029a91839062001760620002ee821b17901c565b156200021557620002aa6200026b565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b600062000305836001600160a01b0384166200030e565b90505b92915050565b60006200031c83836200035d565b620003545750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915562000308565b50600062000308565b60009081526001919091016020526040902054151590565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620003b857805160ff1916838001178555620003e8565b82800160010185558215620003e8579182015b82811115620003e8578251825591602001919060010190620003cb565b50620003f6929150620003fa565b5090565b5b80821115620003f65760008155600101620003fb565b6129e380620004216000396000f3fe608060405234801561001057600080fd5b50600436106101365760003560e01c8063731133e9116100b8578063ca15c8731161007c578063ca15c87314610a3d578063d539139314610a5a578063d547741f14610a62578063e985e9c514610a8e578063f242432a14610abc578063f5298aca14610b8557610136565b8063731133e9146108dc5780639010d07c1461099c57806391d14854146109db578063a217fddf14610a07578063a22cb46514610a0f57610136565b80632eb2c2d6116100ff5780632eb2c2d61461041d5780632f2ff15d146105de57806336568abe1461060a5780634e1273f4146106365780636b20c454146107a957610136565b8062fdd58e1461013b57806301ffc9a7146101795780630e89341c146101b45780631f7fdffa14610246578063248a9ca314610400575b600080fd5b6101676004803603604081101561015157600080fd5b506001600160a01b038135169060200135610bb7565b60408051918252519081900360200190f35b6101a06004803603602081101561018f57600080fd5b50356001600160e01b031916610c29565b604080519115158252519081900360200190f35b6101d1600480360360208110156101ca57600080fd5b5035610c48565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561020b5781810151838201526020016101f3565b50505050905090810190601f1680156102385780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103fe6004803603608081101561025c57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561028657600080fd5b82018360208201111561029857600080fd5b803590602001918460208302840111600160201b831117156102b957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561030857600080fd5b82018360208201111561031a57600080fd5b803590602001918460208302840111600160201b8311171561033b57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561038a57600080fd5b82018360208201111561039c57600080fd5b803590602001918460018302840111600160201b831117156103bd57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610ce0945050505050565b005b6101676004803603602081101561041657600080fd5b5035610d6d565b6103fe600480360360a081101561043357600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561046657600080fd5b82018360208201111561047857600080fd5b803590602001918460208302840111600160201b8311171561049957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156104e857600080fd5b8201836020820111156104fa57600080fd5b803590602001918460208302840111600160201b8311171561051b57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561056a57600080fd5b82018360208201111561057c57600080fd5b803590602001918460018302840111600160201b8311171561059d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610d82945050505050565b6103fe600480360360408110156105f457600080fd5b50803590602001356001600160a01b0316611085565b6103fe6004803603604081101561062057600080fd5b50803590602001356001600160a01b03166110ec565b6107596004803603604081101561064c57600080fd5b810190602081018135600160201b81111561066657600080fd5b82018360208201111561067857600080fd5b803590602001918460208302840111600160201b8311171561069957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156106e857600080fd5b8201836020820111156106fa57600080fd5b803590602001918460208302840111600160201b8311171561071b57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061114d945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561079557818101518382015260200161077d565b505050509050019250505060405180910390f35b6103fe600480360360608110156107bf57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156107e957600080fd5b8201836020820111156107fb57600080fd5b803590602001918460208302840111600160201b8311171561081c57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561086b57600080fd5b82018360208201111561087d57600080fd5b803590602001918460208302840111600160201b8311171561089e57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611239945050505050565b6103fe600480360360808110156108f257600080fd5b6001600160a01b038235169160208101359160408201359190810190608081016060820135600160201b81111561092857600080fd5b82018360208201111561093a57600080fd5b803590602001918460018302840111600160201b8311171561095b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506112b2945050505050565b6109bf600480360360408110156109b257600080fd5b5080359060200135611334565b604080516001600160a01b039092168252519081900360200190f35b6101a0600480360360408110156109f157600080fd5b50803590602001356001600160a01b0316611353565b61016761136b565b6103fe60048036036040811015610a2557600080fd5b506001600160a01b0381351690602001351515611370565b61016760048036036020811015610a5357600080fd5b503561145f565b610167611476565b6103fe60048036036040811015610a7857600080fd5b50803590602001356001600160a01b031661149a565b6101a060048036036040811015610aa457600080fd5b506001600160a01b03813581169160200135166114f3565b6103fe600480360360a0811015610ad257600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b811115610b1157600080fd5b820183602082011115610b2357600080fd5b803590602001918460018302840111600160201b83111715610b4457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611521945050505050565b6103fe60048036036060811015610b9b57600080fd5b506001600160a01b0381351690602081013590604001356116ec565b60006001600160a01b038316610bfe5760405162461bcd60e51b815260040180806020018281038252602b815260200180612798602b913960400191505060405180910390fd5b5060008181526002602090815260408083206001600160a01b03861684529091529020545b92915050565b6001600160e01b03191660009081526001602052604090205460ff1690565b60048054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815260609390929091830182828015610cd45780601f10610ca957610100808354040283529160200191610cd4565b820191906000526020600020905b815481529060010190602001808311610cb757829003601f168201915b50505050509050919050565b610d117f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6610d0c611775565b611353565b610d5b576040805162461bcd60e51b815260206004820152601660248201527529b2b73232b91034b9903737ba10309026b4b73a32b960511b604482015290519081900360640190fd5b610d678484848461177a565b50505050565b60009081526020819052604090206002015490565b8151835114610dc25760405162461bcd60e51b81526004018080602001828103825260288152602001806129366028913960400191505060405180910390fd5b6001600160a01b038416610e075760405162461bcd60e51b81526004018080602001828103825260258152602001806128406025913960400191505060405180910390fd5b610e0f611775565b6001600160a01b0316856001600160a01b03161480610e3a5750610e3a85610e35611775565b6114f3565b610e755760405162461bcd60e51b81526004018080602001828103825260328152602001806128656032913960400191505060405180910390fd5b6000610e7f611775565b9050610e8f81878787878761107d565b60005b8451811015610f95576000858281518110610ea957fe5b602002602001015190506000858381518110610ec157fe5b60200260200101519050610f2e816040518060600160405280602a81526020016128ba602a91396002600086815260200190815260200160002060008d6001600160a01b03166001600160a01b03168152602001908152602001600020546119cf9092919063ffffffff16565b60008381526002602090815260408083206001600160a01b038e811685529252808320939093558a1681522054610f659082611a66565b60009283526002602090815260408085206001600160a01b038c1686529091529092209190915550600101610e92565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561101b578181015183820152602001611003565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561105a578181015183820152602001611042565b5050505090500194505050505060405180910390a461107d818787878787611ac0565b505050505050565b6000828152602081905260409020600201546110a390610d0c611775565b6110de5760405162461bcd60e51b815260040180806020018281038252602f815260200180612769602f913960400191505060405180910390fd5b6110e88282611d3f565b5050565b6110f4611775565b6001600160a01b0316816001600160a01b0316146111435760405162461bcd60e51b815260040180806020018281038252602f81526020018061297f602f913960400191505060405180910390fd5b6110e88282611da8565b6060815183511461118f5760405162461bcd60e51b815260040180806020018281038252602981526020018061290d6029913960400191505060405180910390fd5b6060835167ffffffffffffffff811180156111a957600080fd5b506040519080825280602002602001820160405280156111d3578160200160208202803683370190505b50905060005b8451811015611231576112128582815181106111f157fe5b602002602001015185838151811061120557fe5b6020026020010151610bb7565b82828151811061121e57fe5b60209081029190910101526001016111d9565b509392505050565b611241611775565b6001600160a01b0316836001600160a01b03161480611267575061126783610e35611775565b6112a25760405162461bcd60e51b81526004018080602001828103825260298152602001806127e76029913960400191505060405180910390fd5b6112ad838383611e11565b505050565b6112de7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6610d0c611775565b611328576040805162461bcd60e51b815260206004820152601660248201527529b2b73232b91034b9903737ba10309026b4b73a32b960511b604482015290519081900360640190fd5b610d678484848461207f565b600082815260208190526040812061134c9083612180565b9392505050565b600082815260208190526040812061134c908361218c565b600081565b816001600160a01b0316611382611775565b6001600160a01b031614156113c85760405162461bcd60e51b81526004018080602001828103825260298152602001806128e46029913960400191505060405180910390fd5b80600360006113d5611775565b6001600160a01b03908116825260208083019390935260409182016000908120918716808252919093529120805460ff191692151592909217909155611419611775565b6001600160a01b03167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405180821515815260200191505060405180910390a35050565b6000818152602081905260408120610c23906121a1565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6000828152602081905260409020600201546114b890610d0c611775565b6111435760405162461bcd60e51b81526004018080602001828103825260308152602001806128106030913960400191505060405180910390fd5b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205460ff1690565b6001600160a01b0384166115665760405162461bcd60e51b81526004018080602001828103825260258152602001806128406025913960400191505060405180910390fd5b61156e611775565b6001600160a01b0316856001600160a01b03161480611594575061159485610e35611775565b6115cf5760405162461bcd60e51b81526004018080602001828103825260298152602001806127e76029913960400191505060405180910390fd5b60006115d9611775565b90506115f98187876115ea886121ac565b6115f3886121ac565b8761107d565b611640836040518060600160405280602a81526020016128ba602a913960008781526002602090815260408083206001600160a01b038d16845290915290205491906119cf565b60008581526002602090815260408083206001600160a01b038b811685529252808320939093558716815220546116779084611a66565b60008581526002602090815260408083206001600160a01b03808b168086529184529382902094909455805188815291820187905280518a8416938616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a461107d8187878787876121f0565b6116f4611775565b6001600160a01b0316836001600160a01b0316148061171a575061171a83610e35611775565b6117555760405162461bcd60e51b81526004018080602001828103825260298152602001806127e76029913960400191505060405180910390fd5b6112ad838383612361565b600061134c836001600160a01b038416612494565b335b90565b6001600160a01b0384166117bf5760405162461bcd60e51b815260040180806020018281038252602181526020018061295e6021913960400191505060405180910390fd5b81518351146117ff5760405162461bcd60e51b81526004018080602001828103825260288152602001806129366028913960400191505060405180910390fd5b6000611809611775565b905061181a8160008787878761107d565b60005b84518110156118de576118956002600087848151811061183957fe5b602002602001015181526020019081526020016000206000886001600160a01b03166001600160a01b031681526020019081526020016000205485838151811061187f57fe5b6020026020010151611a6690919063ffffffff16565b600260008784815181106118a557fe5b602090810291909101810151825281810192909252604090810160009081206001600160a01b038b16825290925290205560010161181d565b50846001600160a01b031660006001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561196557818101518382015260200161194d565b50505050905001838103825284818151815260200191508051906020019060200280838360005b838110156119a457818101518382015260200161198c565b5050505090500194505050505060405180910390a46119c881600087878787611ac0565b5050505050565b60008184841115611a5e5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015611a23578181015183820152602001611a0b565b50505050905090810190601f168015611a505780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b60008282018381101561134c576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b611ad2846001600160a01b03166124de565b1561107d57836001600160a01b031663bc197c8187878686866040518663ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b03168152602001806020018060200180602001848103845287818151815260200191508051906020019060200280838360005b83811015611b60578181015183820152602001611b48565b50505050905001848103835286818151815260200191508051906020019060200280838360005b83811015611b9f578181015183820152602001611b87565b50505050905001848103825285818151815260200191508051906020019080838360005b83811015611bdb578181015183820152602001611bc3565b50505050905090810190601f168015611c085780820380516001836020036101000a031916815260200191505b5098505050505050505050602060405180830381600087803b158015611c2d57600080fd5b505af1925050508015611c5257506040513d6020811015611c4d57600080fd5b505160015b611ce757611c5e612645565b80611c695750611cb0565b60405162461bcd60e51b8152602060048201818152835160248401528351849391928392604401919085019080838360008315611a23578181015183820152602001611a0b565b60405162461bcd60e51b81526004018080602001828103825260348152602001806126eb6034913960400191505060405180910390fd5b6001600160e01b0319811663bc197c8160e01b14611d365760405162461bcd60e51b81526004018080602001828103825260288152602001806127416028913960400191505060405180910390fd5b50505050505050565b6000828152602081905260409020611d579082611760565b156110e857611d64611775565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000828152602081905260409020611dc090826124e4565b156110e857611dcd611775565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b6001600160a01b038316611e565760405162461bcd60e51b81526004018080602001828103825260238152602001806128976023913960400191505060405180910390fd5b8051825114611e965760405162461bcd60e51b81526004018080602001828103825260288152602001806129366028913960400191505060405180910390fd5b6000611ea0611775565b9050611ec08185600086866040518060200160405280600081525061107d565b60005b8351811015611f9e57611f55838281518110611edb57fe5b60200260200101516040518060600160405280602481526020016127c36024913960026000888681518110611f0c57fe5b602002602001015181526020019081526020016000206000896001600160a01b03166001600160a01b03168152602001908152602001600020546119cf9092919063ffffffff16565b60026000868481518110611f6557fe5b602090810291909101810151825281810192909252604090810160009081206001600160a01b038a168252909252902055600101611ec3565b5060006001600160a01b0316846001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561202557818101518382015260200161200d565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561206457818101518382015260200161204c565b5050505090500194505050505060405180910390a450505050565b6001600160a01b0384166120c45760405162461bcd60e51b815260040180806020018281038252602181526020018061295e6021913960400191505060405180910390fd5b60006120ce611775565b90506120e0816000876115ea886121ac565b60008481526002602090815260408083206001600160a01b038916845290915290205461210d9084611a66565b60008581526002602090815260408083206001600160a01b03808b16808652918452828520959095558151898152928301889052815190948616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a46119c8816000878787876121f0565b600061134c83836124f9565b600061134c836001600160a01b03841661255d565b6000610c2382612575565b6040805160018082528183019092526060918291906020808301908036833701905050905082816000815181106121df57fe5b602090810291909101015292915050565b612202846001600160a01b03166124de565b1561107d57836001600160a01b031663f23a6e6187878686866040518663ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b0316815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015612291578181015183820152602001612279565b50505050905090810190601f1680156122be5780820380516001836020036101000a031916815260200191505b509650505050505050602060405180830381600087803b1580156122e157600080fd5b505af192505050801561230657506040513d602081101561230157600080fd5b505160015b61231257611c5e612645565b6001600160e01b0319811663f23a6e6160e01b14611d365760405162461bcd60e51b81526004018080602001828103825260288152602001806127416028913960400191505060405180910390fd5b6001600160a01b0383166123a65760405162461bcd60e51b81526004018080602001828103825260238152602001806128976023913960400191505060405180910390fd5b60006123b0611775565b90506123e0818560006123c2876121ac565b6123cb876121ac565b6040518060200160405280600081525061107d565b612427826040518060600160405280602481526020016127c36024913960008681526002602090815260408083206001600160a01b038b16845290915290205491906119cf565b60008481526002602090815260408083206001600160a01b03808a16808652918452828520959095558151888152928301879052815193949093908616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a450505050565b60006124a0838361255d565b6124d657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610c23565b506000610c23565b3b151590565b600061134c836001600160a01b038416612579565b8154600090821061253b5760405162461bcd60e51b815260040180806020018281038252602281526020018061271f6022913960400191505060405180910390fd5b82600001828154811061254a57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b6000818152600183016020526040812054801561263557835460001980830191908101906000908790839081106125ac57fe5b90600052602060002001549050808760000184815481106125c957fe5b6000918252602080832090910192909255828152600189810190925260409020908401905586548790806125f957fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050610c23565b6000915050610c23565b60e01c90565b600060443d101561265557611777565b600481823e6308c379a0612669825161263f565b1461267357611777565b6040513d600319016004823e80513d67ffffffffffffffff81602484011181841117156126a35750505050611777565b828401925082519150808211156126bd5750505050611777565b503d830160208284010111156126d557505050611777565b601f01601f191681016020016040529150509056fe455243313135353a207472616e7366657220746f206e6f6e2045524331313535526563656976657220696d706c656d656e746572456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473455243313135353a204552433131353552656365697665722072656a656374656420746f6b656e73416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e74455243313135353a2062616c616e636520717565727920666f7220746865207a65726f2061646472657373455243313135353a206275726e20616d6f756e7420657863656564732062616c616e6365455243313135353a2063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b65455243313135353a207472616e7366657220746f20746865207a65726f2061646472657373455243313135353a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564455243313135353a206275726e2066726f6d20746865207a65726f2061646472657373455243313135353a20696e73756666696369656e742062616c616e636520666f72207472616e73666572455243313135353a2073657474696e6720617070726f76616c2073746174757320666f722073656c66455243313135353a206163636f756e747320616e6420696473206c656e677468206d69736d61746368455243313135353a2069647320616e6420616d6f756e7473206c656e677468206d69736d61746368455243313135353a206d696e7420746f20746865207a65726f2061646472657373416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a2646970667358221220f6c71086d55c3c512e03d94b4f397b3044846af10939cd9538d8caaae439f5c064736f6c634300060c00339f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101365760003560e01c8063731133e9116100b8578063ca15c8731161007c578063ca15c87314610a3d578063d539139314610a5a578063d547741f14610a62578063e985e9c514610a8e578063f242432a14610abc578063f5298aca14610b8557610136565b8063731133e9146108dc5780639010d07c1461099c57806391d14854146109db578063a217fddf14610a07578063a22cb46514610a0f57610136565b80632eb2c2d6116100ff5780632eb2c2d61461041d5780632f2ff15d146105de57806336568abe1461060a5780634e1273f4146106365780636b20c454146107a957610136565b8062fdd58e1461013b57806301ffc9a7146101795780630e89341c146101b45780631f7fdffa14610246578063248a9ca314610400575b600080fd5b6101676004803603604081101561015157600080fd5b506001600160a01b038135169060200135610bb7565b60408051918252519081900360200190f35b6101a06004803603602081101561018f57600080fd5b50356001600160e01b031916610c29565b604080519115158252519081900360200190f35b6101d1600480360360208110156101ca57600080fd5b5035610c48565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561020b5781810151838201526020016101f3565b50505050905090810190601f1680156102385780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103fe6004803603608081101561025c57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561028657600080fd5b82018360208201111561029857600080fd5b803590602001918460208302840111600160201b831117156102b957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561030857600080fd5b82018360208201111561031a57600080fd5b803590602001918460208302840111600160201b8311171561033b57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561038a57600080fd5b82018360208201111561039c57600080fd5b803590602001918460018302840111600160201b831117156103bd57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610ce0945050505050565b005b6101676004803603602081101561041657600080fd5b5035610d6d565b6103fe600480360360a081101561043357600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561046657600080fd5b82018360208201111561047857600080fd5b803590602001918460208302840111600160201b8311171561049957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156104e857600080fd5b8201836020820111156104fa57600080fd5b803590602001918460208302840111600160201b8311171561051b57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561056a57600080fd5b82018360208201111561057c57600080fd5b803590602001918460018302840111600160201b8311171561059d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610d82945050505050565b6103fe600480360360408110156105f457600080fd5b50803590602001356001600160a01b0316611085565b6103fe6004803603604081101561062057600080fd5b50803590602001356001600160a01b03166110ec565b6107596004803603604081101561064c57600080fd5b810190602081018135600160201b81111561066657600080fd5b82018360208201111561067857600080fd5b803590602001918460208302840111600160201b8311171561069957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156106e857600080fd5b8201836020820111156106fa57600080fd5b803590602001918460208302840111600160201b8311171561071b57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061114d945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561079557818101518382015260200161077d565b505050509050019250505060405180910390f35b6103fe600480360360608110156107bf57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156107e957600080fd5b8201836020820111156107fb57600080fd5b803590602001918460208302840111600160201b8311171561081c57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561086b57600080fd5b82018360208201111561087d57600080fd5b803590602001918460208302840111600160201b8311171561089e57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611239945050505050565b6103fe600480360360808110156108f257600080fd5b6001600160a01b038235169160208101359160408201359190810190608081016060820135600160201b81111561092857600080fd5b82018360208201111561093a57600080fd5b803590602001918460018302840111600160201b8311171561095b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506112b2945050505050565b6109bf600480360360408110156109b257600080fd5b5080359060200135611334565b604080516001600160a01b039092168252519081900360200190f35b6101a0600480360360408110156109f157600080fd5b50803590602001356001600160a01b0316611353565b61016761136b565b6103fe60048036036040811015610a2557600080fd5b506001600160a01b0381351690602001351515611370565b61016760048036036020811015610a5357600080fd5b503561145f565b610167611476565b6103fe60048036036040811015610a7857600080fd5b50803590602001356001600160a01b031661149a565b6101a060048036036040811015610aa457600080fd5b506001600160a01b03813581169160200135166114f3565b6103fe600480360360a0811015610ad257600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b811115610b1157600080fd5b820183602082011115610b2357600080fd5b803590602001918460018302840111600160201b83111715610b4457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611521945050505050565b6103fe60048036036060811015610b9b57600080fd5b506001600160a01b0381351690602081013590604001356116ec565b60006001600160a01b038316610bfe5760405162461bcd60e51b815260040180806020018281038252602b815260200180612798602b913960400191505060405180910390fd5b5060008181526002602090815260408083206001600160a01b03861684529091529020545b92915050565b6001600160e01b03191660009081526001602052604090205460ff1690565b60048054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815260609390929091830182828015610cd45780601f10610ca957610100808354040283529160200191610cd4565b820191906000526020600020905b815481529060010190602001808311610cb757829003601f168201915b50505050509050919050565b610d117f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6610d0c611775565b611353565b610d5b576040805162461bcd60e51b815260206004820152601660248201527529b2b73232b91034b9903737ba10309026b4b73a32b960511b604482015290519081900360640190fd5b610d678484848461177a565b50505050565b60009081526020819052604090206002015490565b8151835114610dc25760405162461bcd60e51b81526004018080602001828103825260288152602001806129366028913960400191505060405180910390fd5b6001600160a01b038416610e075760405162461bcd60e51b81526004018080602001828103825260258152602001806128406025913960400191505060405180910390fd5b610e0f611775565b6001600160a01b0316856001600160a01b03161480610e3a5750610e3a85610e35611775565b6114f3565b610e755760405162461bcd60e51b81526004018080602001828103825260328152602001806128656032913960400191505060405180910390fd5b6000610e7f611775565b9050610e8f81878787878761107d565b60005b8451811015610f95576000858281518110610ea957fe5b602002602001015190506000858381518110610ec157fe5b60200260200101519050610f2e816040518060600160405280602a81526020016128ba602a91396002600086815260200190815260200160002060008d6001600160a01b03166001600160a01b03168152602001908152602001600020546119cf9092919063ffffffff16565b60008381526002602090815260408083206001600160a01b038e811685529252808320939093558a1681522054610f659082611a66565b60009283526002602090815260408085206001600160a01b038c1686529091529092209190915550600101610e92565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561101b578181015183820152602001611003565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561105a578181015183820152602001611042565b5050505090500194505050505060405180910390a461107d818787878787611ac0565b505050505050565b6000828152602081905260409020600201546110a390610d0c611775565b6110de5760405162461bcd60e51b815260040180806020018281038252602f815260200180612769602f913960400191505060405180910390fd5b6110e88282611d3f565b5050565b6110f4611775565b6001600160a01b0316816001600160a01b0316146111435760405162461bcd60e51b815260040180806020018281038252602f81526020018061297f602f913960400191505060405180910390fd5b6110e88282611da8565b6060815183511461118f5760405162461bcd60e51b815260040180806020018281038252602981526020018061290d6029913960400191505060405180910390fd5b6060835167ffffffffffffffff811180156111a957600080fd5b506040519080825280602002602001820160405280156111d3578160200160208202803683370190505b50905060005b8451811015611231576112128582815181106111f157fe5b602002602001015185838151811061120557fe5b6020026020010151610bb7565b82828151811061121e57fe5b60209081029190910101526001016111d9565b509392505050565b611241611775565b6001600160a01b0316836001600160a01b03161480611267575061126783610e35611775565b6112a25760405162461bcd60e51b81526004018080602001828103825260298152602001806127e76029913960400191505060405180910390fd5b6112ad838383611e11565b505050565b6112de7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6610d0c611775565b611328576040805162461bcd60e51b815260206004820152601660248201527529b2b73232b91034b9903737ba10309026b4b73a32b960511b604482015290519081900360640190fd5b610d678484848461207f565b600082815260208190526040812061134c9083612180565b9392505050565b600082815260208190526040812061134c908361218c565b600081565b816001600160a01b0316611382611775565b6001600160a01b031614156113c85760405162461bcd60e51b81526004018080602001828103825260298152602001806128e46029913960400191505060405180910390fd5b80600360006113d5611775565b6001600160a01b03908116825260208083019390935260409182016000908120918716808252919093529120805460ff191692151592909217909155611419611775565b6001600160a01b03167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405180821515815260200191505060405180910390a35050565b6000818152602081905260408120610c23906121a1565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6000828152602081905260409020600201546114b890610d0c611775565b6111435760405162461bcd60e51b81526004018080602001828103825260308152602001806128106030913960400191505060405180910390fd5b6001600160a01b03918216600090815260036020908152604080832093909416825291909152205460ff1690565b6001600160a01b0384166115665760405162461bcd60e51b81526004018080602001828103825260258152602001806128406025913960400191505060405180910390fd5b61156e611775565b6001600160a01b0316856001600160a01b03161480611594575061159485610e35611775565b6115cf5760405162461bcd60e51b81526004018080602001828103825260298152602001806127e76029913960400191505060405180910390fd5b60006115d9611775565b90506115f98187876115ea886121ac565b6115f3886121ac565b8761107d565b611640836040518060600160405280602a81526020016128ba602a913960008781526002602090815260408083206001600160a01b038d16845290915290205491906119cf565b60008581526002602090815260408083206001600160a01b038b811685529252808320939093558716815220546116779084611a66565b60008581526002602090815260408083206001600160a01b03808b168086529184529382902094909455805188815291820187905280518a8416938616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a461107d8187878787876121f0565b6116f4611775565b6001600160a01b0316836001600160a01b0316148061171a575061171a83610e35611775565b6117555760405162461bcd60e51b81526004018080602001828103825260298152602001806127e76029913960400191505060405180910390fd5b6112ad838383612361565b600061134c836001600160a01b038416612494565b335b90565b6001600160a01b0384166117bf5760405162461bcd60e51b815260040180806020018281038252602181526020018061295e6021913960400191505060405180910390fd5b81518351146117ff5760405162461bcd60e51b81526004018080602001828103825260288152602001806129366028913960400191505060405180910390fd5b6000611809611775565b905061181a8160008787878761107d565b60005b84518110156118de576118956002600087848151811061183957fe5b602002602001015181526020019081526020016000206000886001600160a01b03166001600160a01b031681526020019081526020016000205485838151811061187f57fe5b6020026020010151611a6690919063ffffffff16565b600260008784815181106118a557fe5b602090810291909101810151825281810192909252604090810160009081206001600160a01b038b16825290925290205560010161181d565b50846001600160a01b031660006001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561196557818101518382015260200161194d565b50505050905001838103825284818151815260200191508051906020019060200280838360005b838110156119a457818101518382015260200161198c565b5050505090500194505050505060405180910390a46119c881600087878787611ac0565b5050505050565b60008184841115611a5e5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015611a23578181015183820152602001611a0b565b50505050905090810190601f168015611a505780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b60008282018381101561134c576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b611ad2846001600160a01b03166124de565b1561107d57836001600160a01b031663bc197c8187878686866040518663ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b03168152602001806020018060200180602001848103845287818151815260200191508051906020019060200280838360005b83811015611b60578181015183820152602001611b48565b50505050905001848103835286818151815260200191508051906020019060200280838360005b83811015611b9f578181015183820152602001611b87565b50505050905001848103825285818151815260200191508051906020019080838360005b83811015611bdb578181015183820152602001611bc3565b50505050905090810190601f168015611c085780820380516001836020036101000a031916815260200191505b5098505050505050505050602060405180830381600087803b158015611c2d57600080fd5b505af1925050508015611c5257506040513d6020811015611c4d57600080fd5b505160015b611ce757611c5e612645565b80611c695750611cb0565b60405162461bcd60e51b8152602060048201818152835160248401528351849391928392604401919085019080838360008315611a23578181015183820152602001611a0b565b60405162461bcd60e51b81526004018080602001828103825260348152602001806126eb6034913960400191505060405180910390fd5b6001600160e01b0319811663bc197c8160e01b14611d365760405162461bcd60e51b81526004018080602001828103825260288152602001806127416028913960400191505060405180910390fd5b50505050505050565b6000828152602081905260409020611d579082611760565b156110e857611d64611775565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000828152602081905260409020611dc090826124e4565b156110e857611dcd611775565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b6001600160a01b038316611e565760405162461bcd60e51b81526004018080602001828103825260238152602001806128976023913960400191505060405180910390fd5b8051825114611e965760405162461bcd60e51b81526004018080602001828103825260288152602001806129366028913960400191505060405180910390fd5b6000611ea0611775565b9050611ec08185600086866040518060200160405280600081525061107d565b60005b8351811015611f9e57611f55838281518110611edb57fe5b60200260200101516040518060600160405280602481526020016127c36024913960026000888681518110611f0c57fe5b602002602001015181526020019081526020016000206000896001600160a01b03166001600160a01b03168152602001908152602001600020546119cf9092919063ffffffff16565b60026000868481518110611f6557fe5b602090810291909101810151825281810192909252604090810160009081206001600160a01b038a168252909252902055600101611ec3565b5060006001600160a01b0316846001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561202557818101518382015260200161200d565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561206457818101518382015260200161204c565b5050505090500194505050505060405180910390a450505050565b6001600160a01b0384166120c45760405162461bcd60e51b815260040180806020018281038252602181526020018061295e6021913960400191505060405180910390fd5b60006120ce611775565b90506120e0816000876115ea886121ac565b60008481526002602090815260408083206001600160a01b038916845290915290205461210d9084611a66565b60008581526002602090815260408083206001600160a01b03808b16808652918452828520959095558151898152928301889052815190948616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a46119c8816000878787876121f0565b600061134c83836124f9565b600061134c836001600160a01b03841661255d565b6000610c2382612575565b6040805160018082528183019092526060918291906020808301908036833701905050905082816000815181106121df57fe5b602090810291909101015292915050565b612202846001600160a01b03166124de565b1561107d57836001600160a01b031663f23a6e6187878686866040518663ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b0316815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015612291578181015183820152602001612279565b50505050905090810190601f1680156122be5780820380516001836020036101000a031916815260200191505b509650505050505050602060405180830381600087803b1580156122e157600080fd5b505af192505050801561230657506040513d602081101561230157600080fd5b505160015b61231257611c5e612645565b6001600160e01b0319811663f23a6e6160e01b14611d365760405162461bcd60e51b81526004018080602001828103825260288152602001806127416028913960400191505060405180910390fd5b6001600160a01b0383166123a65760405162461bcd60e51b81526004018080602001828103825260238152602001806128976023913960400191505060405180910390fd5b60006123b0611775565b90506123e0818560006123c2876121ac565b6123cb876121ac565b6040518060200160405280600081525061107d565b612427826040518060600160405280602481526020016127c36024913960008681526002602090815260408083206001600160a01b038b16845290915290205491906119cf565b60008481526002602090815260408083206001600160a01b03808a16808652918452828520959095558151888152928301879052815193949093908616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a450505050565b60006124a0838361255d565b6124d657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610c23565b506000610c23565b3b151590565b600061134c836001600160a01b038416612579565b8154600090821061253b5760405162461bcd60e51b815260040180806020018281038252602281526020018061271f6022913960400191505060405180910390fd5b82600001828154811061254a57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b6000818152600183016020526040812054801561263557835460001980830191908101906000908790839081106125ac57fe5b90600052602060002001549050808760000184815481106125c957fe5b6000918252602080832090910192909255828152600189810190925260409020908401905586548790806125f957fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050610c23565b6000915050610c23565b60e01c90565b600060443d101561265557611777565b600481823e6308c379a0612669825161263f565b1461267357611777565b6040513d600319016004823e80513d67ffffffffffffffff81602484011181841117156126a35750505050611777565b828401925082519150808211156126bd5750505050611777565b503d830160208284010111156126d557505050611777565b601f01601f191681016020016040529150509056fe455243313135353a207472616e7366657220746f206e6f6e2045524331313535526563656976657220696d706c656d656e746572456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473455243313135353a204552433131353552656365697665722072656a656374656420746f6b656e73416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e74455243313135353a2062616c616e636520717565727920666f7220746865207a65726f2061646472657373455243313135353a206275726e20616d6f756e7420657863656564732062616c616e6365455243313135353a2063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b65455243313135353a207472616e7366657220746f20746865207a65726f2061646472657373455243313135353a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564455243313135353a206275726e2066726f6d20746865207a65726f2061646472657373455243313135353a20696e73756666696369656e742062616c616e636520666f72207472616e73666572455243313135353a2073657474696e6720617070726f76616c2073746174757320666f722073656c66455243313135353a206163636f756e747320616e6420696473206c656e677468206d69736d61746368455243313135353a2069647320616e6420616d6f756e7473206c656e677468206d69736d61746368455243313135353a206d696e7420746f20746865207a65726f2061646472657373416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a2646970667358221220f6c71086d55c3c512e03d94b4f397b3044846af10939cd9538d8caaae439f5c064736f6c634300060c0033", + "linkReferences": {}, + "deployedLinkReferences": {} + } + \ No newline at end of file diff --git a/test/resources/ERC20Mintable.json b/test/resources/ERC20Mintable.json new file mode 100644 index 00000000..42cbd42f --- /dev/null +++ b/test/resources/ERC20Mintable.json @@ -0,0 +1,1034 @@ +{ + "contractName": "ERC20Mintable", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "recipient", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "name": "addMinter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceMinter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "recipient", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "name": "isMinter", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "account", + "type": "address" + } + ], + "name": "MinterAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "account", + "type": "address" + } + ], + "name": "MinterRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "account", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.5.7+commit.6da8b019\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":false,\"inputs\":[{\"name\":\"spender\",\"type\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"sender\",\"type\":\"address\"},{\"name\":\"recipient\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"spender\",\"type\":\"address\"},{\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"account\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"account\",\"type\":\"address\"}],\"name\":\"addMinter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"renounceMinter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"spender\",\"type\":\"address\"},{\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"recipient\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"account\",\"type\":\"address\"}],\"name\":\"isMinter\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"account\",\"type\":\"address\"}],\"name\":\"MinterAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"account\",\"type\":\"address\"}],\"name\":\"MinterRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"}],\"devdoc\":{\"details\":\"Extension of {ERC20} that adds a set of accounts with the {MinterRole}, which have permission to mint (create) new tokens as they see fit. * At construction, the deployer of the contract is the only minter.\",\"methods\":{\"allowance(address,address)\":{\"details\":\"See {IERC20-allowance}.\"},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. * Requirements: * - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"See {IERC20-balanceOf}.\"},\"decreaseAllowance(address,uint256)\":{\"details\":\"Atomically decreases the allowance granted to `spender` by the caller. * This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. * Emits an {Approval} event indicating the updated allowance. * Requirements: * - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`.\"},\"increaseAllowance(address,uint256)\":{\"details\":\"Atomically increases the allowance granted to `spender` by the caller. * This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. * Emits an {Approval} event indicating the updated allowance. * Requirements: * - `spender` cannot be the zero address.\"},\"mint(address,uint256)\":{\"details\":\"See {ERC20-_mint}. * Requirements: * - the caller must have the {MinterRole}.\"},\"totalSupply()\":{\"details\":\"See {IERC20-totalSupply}.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. * Requirements: * - `recipient` cannot be the zero address. - the caller must have a balance of at least `amount`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. * Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}; * Requirements: - `sender` and `recipient` cannot be the zero address. - `sender` must have a balance of at least `value`. - the caller must have allowance for `sender`'s tokens of at least `amount`.\"}}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/tmp/openzeppelin-contracts/contracts/token/ERC20/ERC20Mintable.sol\":\"ERC20Mintable\"},\"evmVersion\":\"petersburg\",\"libraries\":{},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"/tmp/openzeppelin-contracts/contracts/access/Roles.sol\":{\"keccak256\":\"0xb002c378d7b82a101bd659c341518953ca0919d342c0a400196982c0e7e7bcdb\",\"urls\":[\"bzzr://bd34c1ce05b5b2b3a62fc02e160f6805b1cadd476854664f433c685b2fda8dad\"]},\"/tmp/openzeppelin-contracts/contracts/access/roles/MinterRole.sol\":{\"keccak256\":\"0x63da54a7a5d4e4d82ac8d1f4f7f995fd8ef2b5fe1f2960b83e534fa37fb60910\",\"urls\":[\"bzzr://cea53b805426e40efadb24f2613aca39182462e9ecd23fde7aacf0c4b5885aaf\"]},\"/tmp/openzeppelin-contracts/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x882c8e2c862b2e99bce904d410a01f99010e1e18061359d4b89b69e93cc7f54b\",\"urls\":[\"bzzr://b194a9f40beb234a04a8b4fbf36307a58bf3d22d3ebd2406fc5b385df406ad35\"]},\"/tmp/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol\":{\"keccak256\":\"0x076fad0604bf5f01f817ee3e717720878e10e24602a0cbe141b16e4f815b7686\",\"urls\":[\"bzzr://ccc4f183d48783c7c121767146367eccbc185c1dc3e6c25c4a93a93b63a85bdb\"]},\"/tmp/openzeppelin-contracts/contracts/token/ERC20/ERC20Mintable.sol\":{\"keccak256\":\"0xa2b957cf89692c504962afb7506999155f83385373f808243246cd5879de5940\",\"urls\":[\"bzzr://812521b94ef1bd30b0a93e14a97a0e79e2bf4cb607aa47c21c8177966a79fd1a\"]},\"/tmp/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0xe5bb0f57cff3e299f360052ba50f1ea0fff046df2be070b6943e0e3c3fdad8a9\",\"urls\":[\"bzzr://cf2d583b8dce38d0617fdcd65f2fd9f126fe17b7f683b5a515ea9d2762d8b062\"]}},\"version\":1}", + "bytecode": "0x60806040526100133361001860201b60201c565b610236565b61003081600361007660201b6112531790919060201c565b8073ffffffffffffffffffffffffffffffffffffffff167f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f660405160405180910390a250565b610086828261015760201b60201c565b156100f9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f526f6c65733a206163636f756e7420616c72656164792068617320726f6c650081525060200191505060405180910390fd5b60018260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055505050565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156101df576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180620017d16022913960400191505060405180910390fd5b8260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b61158b80620002466000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c8063983b2d5611610071578063983b2d56146102e7578063986502751461032b578063a457c2d714610335578063a9059cbb1461039b578063aa271e1a14610401578063dd62ed3e1461045d576100b4565b8063095ea7b3146100b957806318160ddd1461011f57806323b872dd1461013d57806339509351146101c357806340c10f191461022957806370a082311461028f575b600080fd5b610105600480360360408110156100cf57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506104d5565b604051808215151515815260200191505060405180910390f35b6101276104ec565b6040518082815260200191505060405180910390f35b6101a96004803603606081101561015357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506104f6565b604051808215151515815260200191505060405180910390f35b61020f600480360360408110156101d957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506105c1565b604051808215151515815260200191505060405180910390f35b6102756004803603604081101561023f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610666565b604051808215151515815260200191505060405180910390f35b6102d1600480360360208110156102a557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506106da565b6040518082815260200191505060405180910390f35b610329600480360360208110156102fd57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610722565b005b61033361078c565b005b6103816004803603604081101561034b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610797565b604051808215151515815260200191505060405180910390f35b6103e7600480360360408110156103b157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610856565b604051808215151515815260200191505060405180910390f35b6104436004803603602081101561041757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061086d565b604051808215151515815260200191505060405180910390f35b6104bf6004803603604081101561047357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061088a565b6040518082815260200191505060405180910390f35b60006104e2338484610911565b6001905092915050565b6000600254905090565b6000610503848484610b08565b6105b684336105b1856040518060600160405280602881526020016114a860289139600160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610dbe9092919063ffffffff16565b610911565b600190509392505050565b600061065c338461065785600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7e90919063ffffffff16565b610911565b6001905092915050565b60006106713361086d565b6106c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260308152602001806114576030913960400191505060405180910390fd5b6106d08383610f06565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b61072b3361086d565b610780576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260308152602001806114576030913960400191505060405180910390fd5b610789816110c1565b50565b6107953361111b565b565b600061084c33846108478560405180606001604052806025815260200161153b60259139600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610dbe9092919063ffffffff16565b610911565b6001905092915050565b6000610863338484610b08565b6001905092915050565b600061088382600361117590919063ffffffff16565b9050919050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610997576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806115176024913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610a1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061140f6022913960400191505060405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610b8e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806114f26025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610c14576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806113ec6023913960400191505060405180910390fd5b610c7f81604051806060016040528060268152602001611431602691396000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610dbe9092919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610d12816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7e90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000838311158290610e6b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610e30578082015181840152602081019050610e15565b50505050905090810190601f168015610e5d5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385039050809150509392505050565b600080828401905083811015610efc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610fa9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45524332303a206d696e7420746f20746865207a65726f20616464726573730081525060200191505060405180910390fd5b610fbe81600254610e7e90919063ffffffff16565b600281905550611015816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7e90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6110d581600361125390919063ffffffff16565b8073ffffffffffffffffffffffffffffffffffffffff167f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f660405160405180910390a250565b61112f81600361132e90919063ffffffff16565b8073ffffffffffffffffffffffffffffffffffffffff167fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669260405160405180910390a250565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156111fc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806114d06022913960400191505060405180910390fd5b8260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b61125d8282611175565b156112d0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f526f6c65733a206163636f756e7420616c72656164792068617320726f6c650081525060200191505060405180910390fd5b60018260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055505050565b6113388282611175565b61138d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806114876021913960400191505060405180910390fd5b60008260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e63654d696e746572526f6c653a2063616c6c657220646f6573206e6f74206861766520746865204d696e74657220726f6c65526f6c65733a206163636f756e7420646f6573206e6f74206861766520726f6c6545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365526f6c65733a206163636f756e7420697320746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa165627a7a72305820daa4c65758945b3ca27e2cfbdf94dd20f65abb62e4c9c953342e20e76cc63aae0029526f6c65733a206163636f756e7420697320746865207a65726f2061646472657373", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100b45760003560e01c8063983b2d5611610071578063983b2d56146102e7578063986502751461032b578063a457c2d714610335578063a9059cbb1461039b578063aa271e1a14610401578063dd62ed3e1461045d576100b4565b8063095ea7b3146100b957806318160ddd1461011f57806323b872dd1461013d57806339509351146101c357806340c10f191461022957806370a082311461028f575b600080fd5b610105600480360360408110156100cf57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506104d5565b604051808215151515815260200191505060405180910390f35b6101276104ec565b6040518082815260200191505060405180910390f35b6101a96004803603606081101561015357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506104f6565b604051808215151515815260200191505060405180910390f35b61020f600480360360408110156101d957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506105c1565b604051808215151515815260200191505060405180910390f35b6102756004803603604081101561023f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610666565b604051808215151515815260200191505060405180910390f35b6102d1600480360360208110156102a557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506106da565b6040518082815260200191505060405180910390f35b610329600480360360208110156102fd57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610722565b005b61033361078c565b005b6103816004803603604081101561034b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610797565b604051808215151515815260200191505060405180910390f35b6103e7600480360360408110156103b157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610856565b604051808215151515815260200191505060405180910390f35b6104436004803603602081101561041757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061086d565b604051808215151515815260200191505060405180910390f35b6104bf6004803603604081101561047357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061088a565b6040518082815260200191505060405180910390f35b60006104e2338484610911565b6001905092915050565b6000600254905090565b6000610503848484610b08565b6105b684336105b1856040518060600160405280602881526020016114a860289139600160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610dbe9092919063ffffffff16565b610911565b600190509392505050565b600061065c338461065785600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7e90919063ffffffff16565b610911565b6001905092915050565b60006106713361086d565b6106c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260308152602001806114576030913960400191505060405180910390fd5b6106d08383610f06565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b61072b3361086d565b610780576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260308152602001806114576030913960400191505060405180910390fd5b610789816110c1565b50565b6107953361111b565b565b600061084c33846108478560405180606001604052806025815260200161153b60259139600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610dbe9092919063ffffffff16565b610911565b6001905092915050565b6000610863338484610b08565b6001905092915050565b600061088382600361117590919063ffffffff16565b9050919050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610997576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806115176024913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610a1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061140f6022913960400191505060405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610b8e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806114f26025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610c14576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806113ec6023913960400191505060405180910390fd5b610c7f81604051806060016040528060268152602001611431602691396000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610dbe9092919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610d12816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7e90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000838311158290610e6b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610e30578082015181840152602081019050610e15565b50505050905090810190601f168015610e5d5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385039050809150509392505050565b600080828401905083811015610efc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610fa9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45524332303a206d696e7420746f20746865207a65726f20616464726573730081525060200191505060405180910390fd5b610fbe81600254610e7e90919063ffffffff16565b600281905550611015816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7e90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6110d581600361125390919063ffffffff16565b8073ffffffffffffffffffffffffffffffffffffffff167f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f660405160405180910390a250565b61112f81600361132e90919063ffffffff16565b8073ffffffffffffffffffffffffffffffffffffffff167fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669260405160405180910390a250565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156111fc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806114d06022913960400191505060405180910390fd5b8260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b61125d8282611175565b156112d0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f526f6c65733a206163636f756e7420616c72656164792068617320726f6c650081525060200191505060405180910390fd5b60018260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055505050565b6113388282611175565b61138d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806114876021913960400191505060405180910390fd5b60008260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e63654d696e746572526f6c653a2063616c6c657220646f6573206e6f74206861766520746865204d696e74657220726f6c65526f6c65733a206163636f756e7420646f6573206e6f74206861766520726f6c6545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365526f6c65733a206163636f756e7420697320746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa165627a7a72305820daa4c65758945b3ca27e2cfbdf94dd20f65abb62e4c9c953342e20e76cc63aae0029", + "sourceMap": "322:322:110:-;;;275:22:2;286:10;275;;;:22;;:::i;:::-;322:322:110;;737:119:2;793:21;806:7;793:8;:12;;;;;;:21;;;;:::i;:::-;841:7;829:20;;;;;;;;;;;;737:119;:::o;260:175:0:-;337:18;341:4;347:7;337:3;;;:18;;:::i;:::-;336:19;328:63;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;424:4;401;:11;;:20;413:7;401:20;;;;;;;;;;;;;;;;:27;;;;;;;;;;;;;;;;;;260:175;;:::o;779:200::-;851:4;894:1;875:21;;:7;:21;;;;867:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;952:4;:11;;:20;964:7;952:20;;;;;;;;;;;;;;;;;;;;;;;;;945:27;;779:200;;;;:::o;322:322:110:-;;;;;;;", + "deployedSourceMap": "322:322:110:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;322:322:110;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2457:145:106;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;2457:145:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;1518:89;;;:::i;:::-;;;;;;;;;;;;;;;;;;;3059:296;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;3059:296:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;3750:203;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;3750:203:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;502:140:110;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;502:140:110;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;1665:108:106;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;1665:108:106;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;560:90:2;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;560:90:2;;;;;;;;;;;;;;;;;;;:::i;:::-;;656:75;;;:::i;:::-;;4440:254:106;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;4440:254:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;1976:153;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;1976:153:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;447:107:2;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;447:107:2;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;2187:132:106;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;2187:132:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;2457:145;2522:4;2538:36;2547:10;2559:7;2568:5;2538:8;:36::i;:::-;2591:4;2584:11;;2457:145;;;;:::o;1518:89::-;1562:7;1588:12;;1581:19;;1518:89;:::o;3059:296::-;3148:4;3164:36;3174:6;3182:9;3193:6;3164:9;:36::i;:::-;3210:117;3219:6;3227:10;3239:87;3275:6;3239:87;;;;;;;;;;;;;;;;;:11;:19;3251:6;3239:19;;;;;;;;;;;;;;;:31;3259:10;3239:31;;;;;;;;;;;;;;;;:35;;:87;;;;;:::i;:::-;3210:8;:117::i;:::-;3344:4;3337:11;;3059:296;;;;;:::o;3750:203::-;3830:4;3846:79;3855:10;3867:7;3876:48;3913:10;3876:11;:23;3888:10;3876:23;;;;;;;;;;;;;;;:32;3900:7;3876:32;;;;;;;;;;;;;;;;:36;;:48;;;;:::i;:::-;3846:8;:79::i;:::-;3942:4;3935:11;;3750:203;;;;:::o;502:140:110:-;576:4;350:20:2;359:10;350:8;:20::i;:::-;342:81;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;592:22:110;598:7;607:6;592:5;:22::i;:::-;631:4;624:11;;502:140;;;;:::o;1665:108:106:-;1722:7;1748:9;:18;1758:7;1748:18;;;;;;;;;;;;;;;;1741:25;;1665:108;;;:::o;560:90:2:-;350:20;359:10;350:8;:20::i;:::-;342:81;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;624:19;635:7;624:10;:19::i;:::-;560:90;:::o;656:75::-;699:25;713:10;699:13;:25::i;:::-;656:75::o;4440:254:106:-;4525:4;4541:125;4550:10;4562:7;4571:94;4608:15;4571:94;;;;;;;;;;;;;;;;;:11;:23;4583:10;4571:23;;;;;;;;;;;;;;;:32;4595:7;4571:32;;;;;;;;;;;;;;;;:36;;:94;;;;;:::i;:::-;4541:8;:125::i;:::-;4683:4;4676:11;;4440:254;;;;:::o;1976:153::-;2045:4;2061:40;2071:10;2083:9;2094:6;2061:9;:40::i;:::-;2118:4;2111:11;;1976:153;;;;:::o;447:107:2:-;503:4;526:21;539:7;526:8;:12;;:21;;;;:::i;:::-;519:28;;447:107;;;:::o;2187:132:106:-;2259:7;2285:11;:18;2297:5;2285:18;;;;;;;;;;;;;;;:27;2304:7;2285:27;;;;;;;;;;;;;;;;2278:34;;2187:132;;;;:::o;7287:329::-;7396:1;7379:19;;:5;:19;;;;7371:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7476:1;7457:21;;:7;:21;;;;7449:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7558:5;7528:11;:18;7540:5;7528:18;;;;;;;;;;;;;;;:27;7547:7;7528:27;;;;;;;;;;;;;;;:35;;;;7594:7;7578:31;;7587:5;7578:31;;;7603:5;7578:31;;;;;;;;;;;;;;;;;;7287:329;;;:::o;5168:464::-;5283:1;5265:20;;:6;:20;;;;5257:70;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5366:1;5345:23;;:9;:23;;;;5337:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5439;5461:6;5439:71;;;;;;;;;;;;;;;;;:9;:17;5449:6;5439:17;;;;;;;;;;;;;;;;:21;;:71;;;;;:::i;:::-;5419:9;:17;5429:6;5419:17;;;;;;;;;;;;;;;:91;;;;5543:32;5568:6;5543:9;:20;5553:9;5543:20;;;;;;;;;;;;;;;;:24;;:32;;;;:::i;:::-;5520:9;:20;5530:9;5520:20;;;;;;;;;;;;;;;:55;;;;5607:9;5590:35;;5599:6;5590:35;;;5618:6;5590:35;;;;;;;;;;;;;;;;;;5168:464;;;:::o;1692:187:40:-;1778:7;1810:1;1805;:6;;1813:12;1797:29;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;1797:29:40;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1836:9;1852:1;1848;:5;1836:17;;1871:1;1864:8;;;1692:187;;;;;:::o;834:176::-;892:7;911:9;927:1;923;:5;911:17;;951:1;946;:6;;938:46;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1002:1;995:8;;;834:176;;;;:::o;5902:302:106:-;5996:1;5977:21;;:7;:21;;;;5969:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6060:24;6077:6;6060:12;;:16;;:24;;;;:::i;:::-;6045:12;:39;;;;6115:30;6138:6;6115:9;:18;6125:7;6115:18;;;;;;;;;;;;;;;;:22;;:30;;;;:::i;:::-;6094:9;:18;6104:7;6094:18;;;;;;;;;;;;;;;:51;;;;6181:7;6160:37;;6177:1;6160:37;;;6190:6;6160:37;;;;;;;;;;;;;;;;;;5902:302;;:::o;737:119:2:-;793:21;806:7;793:8;:12;;:21;;;;:::i;:::-;841:7;829:20;;;;;;;;;;;;737:119;:::o;862:127::-;921:24;937:7;921:8;:15;;:24;;;;:::i;:::-;974:7;960:22;;;;;;;;;;;;862:127;:::o;779:200:0:-;851:4;894:1;875:21;;:7;:21;;;;867:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;952:4;:11;;:20;964:7;952:20;;;;;;;;;;;;;;;;;;;;;;;;;945:27;;779:200;;;;:::o;260:175::-;337:18;341:4;347:7;337:3;:18::i;:::-;336:19;328:63;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;424:4;401;:11;;:20;413:7;401:20;;;;;;;;;;;;;;;;:27;;;;;;;;;;;;;;;;;;260:175;;:::o;510:180::-;589:18;593:4;599:7;589:3;:18::i;:::-;581:64;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;678:5;655:4;:11;;:20;667:7;655:20;;;;;;;;;;;;;;;;:28;;;;;;;;;;;;;;;;;;510:180;;:::o", + "source": "pragma solidity ^0.5.0;\n\nimport \"./ERC20.sol\";\nimport \"../../access/roles/MinterRole.sol\";\n\n/**\n * @dev Extension of {ERC20} that adds a set of accounts with the {MinterRole},\n * which have permission to mint (create) new tokens as they see fit.\n *\n * At construction, the deployer of the contract is the only minter.\n */\ncontract ERC20Mintable is ERC20, MinterRole {\n /**\n * @dev See {ERC20-_mint}.\n *\n * Requirements:\n *\n * - the caller must have the {MinterRole}.\n */\n function mint(address account, uint256 amount) public onlyMinter returns (bool) {\n _mint(account, amount);\n return true;\n }\n}\n", + "sourcePath": "/tmp/openzeppelin-contracts/contracts/token/ERC20/ERC20Mintable.sol", + "ast": { + "absolutePath": "/tmp/openzeppelin-contracts/contracts/token/ERC20/ERC20Mintable.sol", + "exportedSymbols": { + "ERC20Mintable": [ + 8551 + ] + }, + "id": 8552, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 8525, + "literals": [ + "solidity", + "^", + "0.5", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "0:23:110" + }, + { + "absolutePath": "/tmp/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "file": "./ERC20.sol", + "id": 8526, + "nodeType": "ImportDirective", + "scope": 8552, + "sourceUnit": 8376, + "src": "25:21:110", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/tmp/openzeppelin-contracts/contracts/access/roles/MinterRole.sol", + "file": "../../access/roles/MinterRole.sol", + "id": 8527, + "nodeType": "ImportDirective", + "scope": 8552, + "sourceUnit": 289, + "src": "47:43:110", + "symbolAliases": [], + "unitAlias": "" + }, + { + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 8528, + "name": "ERC20", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 8375, + "src": "348:5:110", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$8375", + "typeString": "contract ERC20" + } + }, + "id": 8529, + "nodeType": "InheritanceSpecifier", + "src": "348:5:110" + }, + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 8530, + "name": "MinterRole", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 288, + "src": "355:10:110", + "typeDescriptions": { + "typeIdentifier": "t_contract$_MinterRole_$288", + "typeString": "contract MinterRole" + } + }, + "id": 8531, + "nodeType": "InheritanceSpecifier", + "src": "355:10:110" + } + ], + "contractDependencies": [ + 288, + 8375, + 8722 + ], + "contractKind": "contract", + "documentation": "@dev Extension of {ERC20} that adds a set of accounts with the {MinterRole},\nwhich have permission to mint (create) new tokens as they see fit.\n * At construction, the deployer of the contract is the only minter.", + "fullyImplemented": true, + "id": 8551, + "linearizedBaseContracts": [ + 8551, + 288, + 8375, + 8722 + ], + "name": "ERC20Mintable", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": { + "id": 8549, + "nodeType": "Block", + "src": "582:60:110", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 8543, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 8533, + "src": "598:7:110", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 8544, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 8535, + "src": "607:6:110", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 8542, + "name": "_mint", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 8259, + "src": "592:5:110", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 8545, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "592:22:110", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 8546, + "nodeType": "ExpressionStatement", + "src": "592:22:110" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 8547, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "631:4:110", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 8541, + "id": 8548, + "nodeType": "Return", + "src": "624:11:110" + } + ] + }, + "documentation": "@dev See {ERC20-_mint}.\n * Requirements:\n * - the caller must have the {MinterRole}.", + "id": 8550, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 8538, + "modifierName": { + "argumentTypes": null, + "id": 8537, + "name": "onlyMinter", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 221, + "src": "556:10:110", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "556:10:110" + } + ], + "name": "mint", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 8536, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 8533, + "name": "account", + "nodeType": "VariableDeclaration", + "scope": 8550, + "src": "516:15:110", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 8532, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "516:7:110", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 8535, + "name": "amount", + "nodeType": "VariableDeclaration", + "scope": 8550, + "src": "533:14:110", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 8534, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "533:7:110", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "515:33:110" + }, + "returnParameters": { + "id": 8541, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 8540, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 8550, + "src": "576:4:110", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 8539, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "576:4:110", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "575:6:110" + }, + "scope": 8551, + "src": "502:140:110", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + } + ], + "scope": 8552, + "src": "322:322:110" + } + ], + "src": "0:645:110" + }, + "legacyAST": { + "absolutePath": "/tmp/openzeppelin-contracts/contracts/token/ERC20/ERC20Mintable.sol", + "exportedSymbols": { + "ERC20Mintable": [ + 8551 + ] + }, + "id": 8552, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 8525, + "literals": [ + "solidity", + "^", + "0.5", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "0:23:110" + }, + { + "absolutePath": "/tmp/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "file": "./ERC20.sol", + "id": 8526, + "nodeType": "ImportDirective", + "scope": 8552, + "sourceUnit": 8376, + "src": "25:21:110", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/tmp/openzeppelin-contracts/contracts/access/roles/MinterRole.sol", + "file": "../../access/roles/MinterRole.sol", + "id": 8527, + "nodeType": "ImportDirective", + "scope": 8552, + "sourceUnit": 289, + "src": "47:43:110", + "symbolAliases": [], + "unitAlias": "" + }, + { + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 8528, + "name": "ERC20", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 8375, + "src": "348:5:110", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$8375", + "typeString": "contract ERC20" + } + }, + "id": 8529, + "nodeType": "InheritanceSpecifier", + "src": "348:5:110" + }, + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 8530, + "name": "MinterRole", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 288, + "src": "355:10:110", + "typeDescriptions": { + "typeIdentifier": "t_contract$_MinterRole_$288", + "typeString": "contract MinterRole" + } + }, + "id": 8531, + "nodeType": "InheritanceSpecifier", + "src": "355:10:110" + } + ], + "contractDependencies": [ + 288, + 8375, + 8722 + ], + "contractKind": "contract", + "documentation": "@dev Extension of {ERC20} that adds a set of accounts with the {MinterRole},\nwhich have permission to mint (create) new tokens as they see fit.\n * At construction, the deployer of the contract is the only minter.", + "fullyImplemented": true, + "id": 8551, + "linearizedBaseContracts": [ + 8551, + 288, + 8375, + 8722 + ], + "name": "ERC20Mintable", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": { + "id": 8549, + "nodeType": "Block", + "src": "582:60:110", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 8543, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 8533, + "src": "598:7:110", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 8544, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 8535, + "src": "607:6:110", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 8542, + "name": "_mint", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 8259, + "src": "592:5:110", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 8545, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "592:22:110", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 8546, + "nodeType": "ExpressionStatement", + "src": "592:22:110" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 8547, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "631:4:110", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 8541, + "id": 8548, + "nodeType": "Return", + "src": "624:11:110" + } + ] + }, + "documentation": "@dev See {ERC20-_mint}.\n * Requirements:\n * - the caller must have the {MinterRole}.", + "id": 8550, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 8538, + "modifierName": { + "argumentTypes": null, + "id": 8537, + "name": "onlyMinter", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 221, + "src": "556:10:110", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "556:10:110" + } + ], + "name": "mint", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 8536, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 8533, + "name": "account", + "nodeType": "VariableDeclaration", + "scope": 8550, + "src": "516:15:110", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 8532, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "516:7:110", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 8535, + "name": "amount", + "nodeType": "VariableDeclaration", + "scope": 8550, + "src": "533:14:110", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 8534, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "533:7:110", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "515:33:110" + }, + "returnParameters": { + "id": 8541, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 8540, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 8550, + "src": "576:4:110", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 8539, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "576:4:110", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "575:6:110" + }, + "scope": 8551, + "src": "502:140:110", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + } + ], + "scope": 8552, + "src": "322:322:110" + } + ], + "src": "0:645:110" + }, + "compiler": { + "name": "solc", + "version": "0.5.7+commit.6da8b019.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.0.11", + "updatedAt": "2019-08-07T12:44:51.917Z", + "devdoc": { + "details": "Extension of {ERC20} that adds a set of accounts with the {MinterRole}, which have permission to mint (create) new tokens as they see fit. * At construction, the deployer of the contract is the only minter.", + "methods": { + "allowance(address,address)": { + "details": "See {IERC20-allowance}." + }, + "approve(address,uint256)": { + "details": "See {IERC20-approve}. * Requirements: * - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "See {IERC20-balanceOf}." + }, + "decreaseAllowance(address,uint256)": { + "details": "Atomically decreases the allowance granted to `spender` by the caller. * This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. * Emits an {Approval} event indicating the updated allowance. * Requirements: * - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`." + }, + "increaseAllowance(address,uint256)": { + "details": "Atomically increases the allowance granted to `spender` by the caller. * This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. * Emits an {Approval} event indicating the updated allowance. * Requirements: * - `spender` cannot be the zero address." + }, + "mint(address,uint256)": { + "details": "See {ERC20-_mint}. * Requirements: * - the caller must have the {MinterRole}." + }, + "totalSupply()": { + "details": "See {IERC20-totalSupply}." + }, + "transfer(address,uint256)": { + "details": "See {IERC20-transfer}. * Requirements: * - `recipient` cannot be the zero address. - the caller must have a balance of at least `amount`." + }, + "transferFrom(address,address,uint256)": { + "details": "See {IERC20-transferFrom}. * Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}; * Requirements: - `sender` and `recipient` cannot be the zero address. - `sender` must have a balance of at least `value`. - the caller must have allowance for `sender`'s tokens of at least `amount`." + } + } + }, + "userdoc": { + "methods": {} + } +} \ No newline at end of file diff --git a/test/resources/ERC20MintableDetailed.json b/test/resources/ERC20MintableDetailed.json new file mode 100644 index 00000000..d4a3ac88 --- /dev/null +++ b/test/resources/ERC20MintableDetailed.json @@ -0,0 +1,2137 @@ +{ + "contractName": "VASYA20", + "abi": [ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "addMinter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceMinter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "isMinter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MinterAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MinterRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.5.11+commit.c082d0b4\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burnFrom\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"addMinter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"renounceMinter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"isMinter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"symbol\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"MinterAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"MinterRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"}],\"devdoc\":{\"methods\":{\"allowance(address,address)\":{\"details\":\"See `IERC20.allowance`.\"},\"approve(address,uint256)\":{\"details\":\"See `IERC20.approve`. * Requirements: * - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"See `IERC20.balanceOf`.\"},\"decimals()\":{\"details\":\"Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5,05` (`505 / 10 ** 2`). * Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. * > Note that this information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including `IERC20.balanceOf` and `IERC20.transfer`.\"},\"decreaseAllowance(address,uint256)\":{\"details\":\"Atomically decreases the allowance granted to `spender` by the caller. * This is an alternative to `approve` that can be used as a mitigation for problems described in `IERC20.approve`. * Emits an `Approval` event indicating the updated allowance. * Requirements: * - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`.\"},\"increaseAllowance(address,uint256)\":{\"details\":\"Atomically increases the allowance granted to `spender` by the caller. * This is an alternative to `approve` that can be used as a mitigation for problems described in `IERC20.approve`. * Emits an `Approval` event indicating the updated allowance. * Requirements: * - `spender` cannot be the zero address.\"},\"name()\":{\"details\":\"Returns the name of the token.\"},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"totalSupply()\":{\"details\":\"See `IERC20.totalSupply`.\"},\"transfer(address,uint256)\":{\"details\":\"See `IERC20.transfer`. * Requirements: * - `recipient` cannot be the zero address. - the caller must have a balance of at least `amount`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See `IERC20.transferFrom`. * Emits an `Approval` event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of `ERC20`; * Requirements: - `sender` and `recipient` cannot be the zero address. - `sender` must have a balance of at least `value`. - the caller must have allowance for `sender`'s tokens of at least `amount`.\"}}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/home/elvis/work/d2/erc721/contracts/VASYA20.sol\":\"VASYA20\"},\"evmVersion\":\"byzantium\",\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/elvis/work/d2/erc721/contracts/VASYA20.sol\":{\"keccak256\":\"0xd1faac21fda946a6dff862486f43b33338b8d0c9d3228225c24b2d44554ac5c6\",\"urls\":[\"bzz-raw://4e65abeddeec783a4ec6f69de5da240ac02be226d6e1eed08d88bc8f5f28902e\",\"dweb:/ipfs/QmTfiY9XwC3HwbifKd3NNasQM8EBVh5xdy4R5gK4XGsmkU\"]},\"openzeppelin-solidity/contracts/access/Roles.sol\":{\"keccak256\":\"0xb002c378d7b82a101bd659c341518953ca0919d342c0a400196982c0e7e7bcdb\",\"urls\":[\"bzz-raw://00a788c4631466c220b385bdd100c571d24b2deccd657615cfbcef6cadf669a4\",\"dweb:/ipfs/QmTEwDbjJNxmMNCDMqtuou3dyM8Wtp8Q9NFvn7SAVM7Jf3\"]},\"openzeppelin-solidity/contracts/access/roles/MinterRole.sol\":{\"keccak256\":\"0x63da54a7a5d4e4d82ac8d1f4f7f995fd8ef2b5fe1f2960b83e534fa37fb60910\",\"urls\":[\"bzz-raw://22e54aa25d633d51efaadb0c956ddd80616a79ee79a41bb8d958473712732612\",\"dweb:/ipfs/QmNbiwWkL4v1i6TgrppGahxHBUHJUrLJLVc8EDb7DFsVDq\"]},\"openzeppelin-solidity/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x4ccf2d7b51873db1ccfd54ca2adae5eac3b184f9699911ed4490438419f1c690\",\"urls\":[\"bzz-raw://d62d769b2219d5de39013093412623e624fa887f871826ea3bae6052ee893610\",\"dweb:/ipfs/QmV3yVktya1s617QmuzQR2CfuJgUi3dR2xEZY9ecmqZ2G1\"]},\"openzeppelin-solidity/contracts/token/ERC20/ERC20.sol\":{\"keccak256\":\"0x852793a3c2f86d336a683b30d688ec3dcfc57451af5a2bf5975cda3b7191a901\",\"urls\":[\"bzz-raw://4f5b57664069671648fb81f55b0082faecdf1b2f159eec6b1fa6cef9b7d73bc5\",\"dweb:/ipfs/QmcyytaLs7zFdb4Uu7C5PmQRhQdB3wA3fUdkV6mkYfdDFH\"]},\"openzeppelin-solidity/contracts/token/ERC20/ERC20Capped.sol\":{\"keccak256\":\"0x9502ccd52b96828ec0a53acbdc8873104d80388a98651a4640c7c5cb5d378fe0\",\"urls\":[\"bzz-raw://337cd68e894f25d8111aa6fc8678a296c2ca138fc71f813c9930b7fbaaf2db93\",\"dweb:/ipfs/QmXRagnqKFX4UWNmPy9sQEDEGqzvRxmFY95epQVMqLMdUR\"]},\"openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol\":{\"keccak256\":\"0xc61b3603089b09a730d8ca72e9133a496cc4405da40e9b87c12f073245d774bf\",\"urls\":[\"bzz-raw://de8bb0003d53de236913f0e0102e7a9d015e02098f2495edd000f207fe2be2f4\",\"dweb:/ipfs/QmbtwNwAJEehWWL7yGGyyMoenQvcqtz91pqLgQPpLRoLYC\"]},\"openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol\":{\"keccak256\":\"0x6df8d686813b2875e99928ecd07bf8ee6d7473bc6a95eb1190e8fdba86beda76\",\"urls\":[\"bzz-raw://ab0eb6779dbe75fcbae03ef6ec5d0345ff232750684c8341fc55f175bf43d13c\",\"dweb:/ipfs/QmcEyprFynsXawyHsgWMWGt7RoVV9tAt8Pac7QC1qEd7Bs\"]},\"openzeppelin-solidity/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x90e8c2521653bbb1768b05889c5760031e688d9cd361f167489b89215e201b95\",\"urls\":[\"bzz-raw://d0abb99bb8bfc2bc0a89902b8ed1dc0442ad08cc78cee64c291b3df6a27bcccc\",\"dweb:/ipfs/QmP5NaEwZthQeM2ESz4WTT3osrP7jhbvu7ocbttBi2JAw6\"]}},\"version\":1}", + "bytecode": "0x60806040523480156200001157600080fd5b50604051620013d7380380620013d7833981810160405260608110156200003757600080fd5b81019080805160405193929190846401000000008211156200005857600080fd5b9083019060208201858111156200006e57600080fd5b82516401000000008111828201881017156200008957600080fd5b82525081516020918201929091019080838360005b83811015620000b85781810151838201526020016200009e565b50505050905090810190601f168015620000e65780820380516001836020036101000a031916815260200191505b50604052602001805160405193929190846401000000008211156200010a57600080fd5b9083019060208201858111156200012057600080fd5b82516401000000008111828201881017156200013b57600080fd5b82525081516020918201929091019080838360005b838110156200016a57818101518382015260200162000150565b50505050905090810190601f168015620001985780820380516001836020036101000a031916815260200191505b50604052602090810151855190935085925084918491620001c0916000919086019062000387565b508151620001d690600190602085019062000387565b506002805460ff191660ff9290921691909117905550620002029050336401000000006200020b810204565b5050506200042c565b6200022660068264010000000062000d346200025d82021704565b604051600160a060020a038216907f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f690600090a250565b62000272828264010000000062000304810204565b15620002df57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f526f6c65733a206163636f756e7420616c72656164792068617320726f6c6500604482015290519081900360640190fd5b600160a060020a0316600090815260209190915260409020805460ff19166001179055565b6000600160a060020a03821662000367576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180620013b56022913960400191505060405180910390fd5b50600160a060020a03166000908152602091909152604090205460ff1690565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620003ca57805160ff1916838001178555620003fa565b82800160010185558215620003fa579182015b82811115620003fa578251825591602001919060010190620003dd565b50620004089291506200040c565b5090565b6200042991905b8082111562000408576000815560010162000413565b90565b610f79806200043c6000396000f3fe608060405234801561001057600080fd5b5060043610610128576000357c01000000000000000000000000000000000000000000000000000000009004806370a08231116100bf578063986502751161008e578063986502751461034f578063a457c2d714610357578063a9059cbb14610383578063aa271e1a146103af578063dd62ed3e146103d557610128565b806370a08231146102cf57806379cc6790146102f557806395d89b4114610321578063983b2d561461032957610128565b8063313ce567116100fb578063313ce5671461023a578063395093511461025857806340c10f191461028457806342966c68146102b057610128565b806306fdde031461012d578063095ea7b3146101aa57806318160ddd146101ea57806323b872dd14610204575b600080fd5b610135610403565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561016f578181015183820152602001610157565b50505050905090810190601f16801561019c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101d6600480360360408110156101c057600080fd5b50600160a060020a038135169060200135610499565b604080519115158252519081900360200190f35b6101f26104af565b60408051918252519081900360200190f35b6101d66004803603606081101561021a57600080fd5b50600160a060020a038135811691602081013590911690604001356104b5565b61024261050c565b6040805160ff9092168252519081900360200190f35b6101d66004803603604081101561026e57600080fd5b50600160a060020a038135169060200135610515565b6101d66004803603604081101561029a57600080fd5b50600160a060020a038135169060200135610551565b6102cd600480360360208110156102c657600080fd5b50356105a4565b005b6101f2600480360360208110156102e557600080fd5b5035600160a060020a03166105b1565b6102cd6004803603604081101561030b57600080fd5b50600160a060020a0381351690602001356105cc565b6101356105da565b6102cd6004803603602081101561033f57600080fd5b5035600160a060020a031661063a565b6102cd61068a565b6101d66004803603604081101561036d57600080fd5b50600160a060020a038135169060200135610695565b6101d66004803603604081101561039957600080fd5b50600160a060020a0381351690602001356106d1565b6101d6600480360360208110156103c557600080fd5b5035600160a060020a03166106de565b6101f2600480360360408110156103eb57600080fd5b50600160a060020a03813581169160200135166106f7565b60008054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561048f5780601f106104645761010080835404028352916020019161048f565b820191906000526020600020905b81548152906001019060200180831161047257829003601f168201915b5050505050905090565b60006104a6338484610722565b50600192915050565b60055490565b60006104c2848484610814565b600160a060020a0384166000908152600460209081526040808320338085529252909120546105029186916104fd908663ffffffff61095e16565b610722565b5060019392505050565b60025460ff1690565b336000818152600460209081526040808320600160a060020a038716845290915281205490916104a69185906104fd908663ffffffff6109be16565b600061055c336106de565b61059a5760405160e560020a62461bcd028152600401808060200182810382526030815260200180610e686030913960400191505060405180910390fd5b6104a68383610a22565b6105ae3382610b17565b50565b600160a060020a031660009081526003602052604090205490565b6105d68282610bf5565b5050565b60018054604080516020601f6002600019610100878916150201909516949094049384018190048102820181019092528281526060939092909183018282801561048f5780601f106104645761010080835404028352916020019161048f565b610643336106de565b6106815760405160e560020a62461bcd028152600401808060200182810382526030815260200180610e686030913960400191505060405180910390fd5b6105ae81610c3a565b61069333610c82565b565b336000818152600460209081526040808320600160a060020a038716845290915281205490916104a69185906104fd908663ffffffff61095e16565b60006104a6338484610814565b60006106f160068363ffffffff610cca16565b92915050565b600160a060020a03918216600090815260046020908152604080832093909416825291909152205490565b600160a060020a03831661076a5760405160e560020a62461bcd028152600401808060200182810382526024815260200180610f216024913960400191505060405180910390fd5b600160a060020a0382166107b25760405160e560020a62461bcd028152600401808060200182810382526022815260200180610e466022913960400191505060405180910390fd5b600160a060020a03808416600081815260046020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b600160a060020a03831661085c5760405160e560020a62461bcd028152600401808060200182810382526025815260200180610efc6025913960400191505060405180910390fd5b600160a060020a0382166108a45760405160e560020a62461bcd028152600401808060200182810382526023815260200180610e236023913960400191505060405180910390fd5b600160a060020a0383166000908152600360205260409020546108cd908263ffffffff61095e16565b600160a060020a038085166000908152600360205260408082209390935590841681522054610902908263ffffffff6109be16565b600160a060020a0380841660008181526003602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b6000828211156109b8576040805160e560020a62461bcd02815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b600082820183811015610a1b576040805160e560020a62461bcd02815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b600160a060020a038216610a80576040805160e560020a62461bcd02815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b600554610a93908263ffffffff6109be16565b600555600160a060020a038216600090815260036020526040902054610abf908263ffffffff6109be16565b600160a060020a03831660008181526003602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b600160a060020a038216610b5f5760405160e560020a62461bcd028152600401808060200182810382526021815260200180610edb6021913960400191505060405180910390fd5b600554610b72908263ffffffff61095e16565b600555600160a060020a038216600090815260036020526040902054610b9e908263ffffffff61095e16565b600160a060020a0383166000818152600360209081526040808320949094558351858152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35050565b610bff8282610b17565b600160a060020a0382166000908152600460209081526040808320338085529252909120546105d69184916104fd908563ffffffff61095e16565b610c4b60068263ffffffff610d3416565b604051600160a060020a038216907f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f690600090a250565b610c9360068263ffffffff610db816565b604051600160a060020a038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669290600090a250565b6000600160a060020a038216610d145760405160e560020a62461bcd028152600401808060200182810382526022815260200180610eb96022913960400191505060405180910390fd5b50600160a060020a03166000908152602091909152604090205460ff1690565b610d3e8282610cca565b15610d93576040805160e560020a62461bcd02815260206004820152601f60248201527f526f6c65733a206163636f756e7420616c72656164792068617320726f6c6500604482015290519081900360640190fd5b600160a060020a0316600090815260209190915260409020805460ff19166001179055565b610dc28282610cca565b610e005760405160e560020a62461bcd028152600401808060200182810382526021815260200180610e986021913960400191505060405180910390fd5b600160a060020a0316600090815260209190915260409020805460ff1916905556fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f20616464726573734d696e746572526f6c653a2063616c6c657220646f6573206e6f74206861766520746865204d696e74657220726f6c65526f6c65733a206163636f756e7420646f6573206e6f74206861766520726f6c65526f6c65733a206163636f756e7420697320746865207a65726f206164647265737345524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f2061646472657373a265627a7a72315820f0f3bd370deb19dd64d9036c7388685af5eccdcc97286e7410dabcd6b41a0b7764736f6c634300050b0032526f6c65733a206163636f756e7420697320746865207a65726f2061646472657373", + "deployedBytecode": "0x608060405234801561001057600080fd5b5060043610610128576000357c01000000000000000000000000000000000000000000000000000000009004806370a08231116100bf578063986502751161008e578063986502751461034f578063a457c2d714610357578063a9059cbb14610383578063aa271e1a146103af578063dd62ed3e146103d557610128565b806370a08231146102cf57806379cc6790146102f557806395d89b4114610321578063983b2d561461032957610128565b8063313ce567116100fb578063313ce5671461023a578063395093511461025857806340c10f191461028457806342966c68146102b057610128565b806306fdde031461012d578063095ea7b3146101aa57806318160ddd146101ea57806323b872dd14610204575b600080fd5b610135610403565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561016f578181015183820152602001610157565b50505050905090810190601f16801561019c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101d6600480360360408110156101c057600080fd5b50600160a060020a038135169060200135610499565b604080519115158252519081900360200190f35b6101f26104af565b60408051918252519081900360200190f35b6101d66004803603606081101561021a57600080fd5b50600160a060020a038135811691602081013590911690604001356104b5565b61024261050c565b6040805160ff9092168252519081900360200190f35b6101d66004803603604081101561026e57600080fd5b50600160a060020a038135169060200135610515565b6101d66004803603604081101561029a57600080fd5b50600160a060020a038135169060200135610551565b6102cd600480360360208110156102c657600080fd5b50356105a4565b005b6101f2600480360360208110156102e557600080fd5b5035600160a060020a03166105b1565b6102cd6004803603604081101561030b57600080fd5b50600160a060020a0381351690602001356105cc565b6101356105da565b6102cd6004803603602081101561033f57600080fd5b5035600160a060020a031661063a565b6102cd61068a565b6101d66004803603604081101561036d57600080fd5b50600160a060020a038135169060200135610695565b6101d66004803603604081101561039957600080fd5b50600160a060020a0381351690602001356106d1565b6101d6600480360360208110156103c557600080fd5b5035600160a060020a03166106de565b6101f2600480360360408110156103eb57600080fd5b50600160a060020a03813581169160200135166106f7565b60008054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561048f5780601f106104645761010080835404028352916020019161048f565b820191906000526020600020905b81548152906001019060200180831161047257829003601f168201915b5050505050905090565b60006104a6338484610722565b50600192915050565b60055490565b60006104c2848484610814565b600160a060020a0384166000908152600460209081526040808320338085529252909120546105029186916104fd908663ffffffff61095e16565b610722565b5060019392505050565b60025460ff1690565b336000818152600460209081526040808320600160a060020a038716845290915281205490916104a69185906104fd908663ffffffff6109be16565b600061055c336106de565b61059a5760405160e560020a62461bcd028152600401808060200182810382526030815260200180610e686030913960400191505060405180910390fd5b6104a68383610a22565b6105ae3382610b17565b50565b600160a060020a031660009081526003602052604090205490565b6105d68282610bf5565b5050565b60018054604080516020601f6002600019610100878916150201909516949094049384018190048102820181019092528281526060939092909183018282801561048f5780601f106104645761010080835404028352916020019161048f565b610643336106de565b6106815760405160e560020a62461bcd028152600401808060200182810382526030815260200180610e686030913960400191505060405180910390fd5b6105ae81610c3a565b61069333610c82565b565b336000818152600460209081526040808320600160a060020a038716845290915281205490916104a69185906104fd908663ffffffff61095e16565b60006104a6338484610814565b60006106f160068363ffffffff610cca16565b92915050565b600160a060020a03918216600090815260046020908152604080832093909416825291909152205490565b600160a060020a03831661076a5760405160e560020a62461bcd028152600401808060200182810382526024815260200180610f216024913960400191505060405180910390fd5b600160a060020a0382166107b25760405160e560020a62461bcd028152600401808060200182810382526022815260200180610e466022913960400191505060405180910390fd5b600160a060020a03808416600081815260046020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b600160a060020a03831661085c5760405160e560020a62461bcd028152600401808060200182810382526025815260200180610efc6025913960400191505060405180910390fd5b600160a060020a0382166108a45760405160e560020a62461bcd028152600401808060200182810382526023815260200180610e236023913960400191505060405180910390fd5b600160a060020a0383166000908152600360205260409020546108cd908263ffffffff61095e16565b600160a060020a038085166000908152600360205260408082209390935590841681522054610902908263ffffffff6109be16565b600160a060020a0380841660008181526003602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b6000828211156109b8576040805160e560020a62461bcd02815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b600082820183811015610a1b576040805160e560020a62461bcd02815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b600160a060020a038216610a80576040805160e560020a62461bcd02815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b600554610a93908263ffffffff6109be16565b600555600160a060020a038216600090815260036020526040902054610abf908263ffffffff6109be16565b600160a060020a03831660008181526003602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b600160a060020a038216610b5f5760405160e560020a62461bcd028152600401808060200182810382526021815260200180610edb6021913960400191505060405180910390fd5b600554610b72908263ffffffff61095e16565b600555600160a060020a038216600090815260036020526040902054610b9e908263ffffffff61095e16565b600160a060020a0383166000818152600360209081526040808320949094558351858152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35050565b610bff8282610b17565b600160a060020a0382166000908152600460209081526040808320338085529252909120546105d69184916104fd908563ffffffff61095e16565b610c4b60068263ffffffff610d3416565b604051600160a060020a038216907f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f690600090a250565b610c9360068263ffffffff610db816565b604051600160a060020a038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669290600090a250565b6000600160a060020a038216610d145760405160e560020a62461bcd028152600401808060200182810382526022815260200180610eb96022913960400191505060405180910390fd5b50600160a060020a03166000908152602091909152604090205460ff1690565b610d3e8282610cca565b15610d93576040805160e560020a62461bcd02815260206004820152601f60248201527f526f6c65733a206163636f756e7420616c72656164792068617320726f6c6500604482015290519081900360640190fd5b600160a060020a0316600090815260209190915260409020805460ff19166001179055565b610dc28282610cca565b610e005760405160e560020a62461bcd028152600401808060200182810382526021815260200180610e986021913960400191505060405180910390fd5b600160a060020a0316600090815260209190915260409020805460ff1916905556fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f20616464726573734d696e746572526f6c653a2063616c6c657220646f6573206e6f74206861766520746865204d696e74657220726f6c65526f6c65733a206163636f756e7420646f6573206e6f74206861766520726f6c65526f6c65733a206163636f756e7420697320746865207a65726f206164647265737345524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f2061646472657373a265627a7a72315820f0f3bd370deb19dd64d9036c7388685af5eccdcc97286e7410dabcd6b41a0b7764736f6c634300050b0032", + "sourceMap": "169:656:1:-;;;225:233;8:9:-1;5:2;;;30:1;27;20:12;5:2;225:233:1;;;;;;;;;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;225:233:1;;;;;;;;;;;;;19:11:-1;14:3;11:20;8:2;;;44:1;41;34:12;8:2;62:21;;;;123:4;114:14;;138:31;;;135:2;;;182:1;179;172:12;135:2;213:10;;261:11;244:29;;285:43;;;282:58;-1:-1;233:115;230:2;;;361:1;358;351:12;230:2;372:25;;-1:-1;225:233:1;;420:4:-1;411:14;;;;225:233:1;;;;;411:14:-1;225:233:1;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;225:233:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;19:11:-1;14:3;11:20;8:2;;;44:1;41;34:12;8:2;62:21;;;;123:4;114:14;;138:31;;;135:2;;;182:1;179;172:12;135:2;213:10;;261:11;244:29;;285:43;;;282:58;-1:-1;233:115;230:2;;;361:1;358;351:12;230:2;372:25;;-1:-1;225:233:1;;420:4:-1;411:14;;;;225:233:1;;;;;411:14:-1;225:233:1;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;225:233:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;225:233:1;;;;;;;504:12:86;;225:233:1;;-1:-1:-1;351:4:1;;-1:-1:-1;357:6:1;;225:233;;504:12:86;;:5;;:12;;;;;:::i;:::-;-1:-1:-1;526:16:86;;;;:7;;:16;;;;;:::i;:::-;-1:-1:-1;552:9:86;:20;;-1:-1:-1;;552:20:86;;;;;;;;;;;;-1:-1:-1;275:22:79;;-1:-1:-1;286:10:79;275;;;;:22;:::i;:::-;225:233:1;;;169:656;;737:119:79;793:21;:8;806:7;793:12;;;;;;:21;:::i;:::-;829:20;;-1:-1:-1;;;;;829:20:79;;;;;;;;737:119;:::o;260:175:78:-;337:18;341:4;347:7;337:3;;;;:18;:::i;:::-;336:19;328:63;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;401:20:78;:11;:20;;;;;;;;;;;:27;;-1:-1:-1;;401:27:78;424:4;401:27;;;260:175::o;779:200::-;851:4;-1:-1:-1;;;;;875:21:78;;867:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;952:20:78;:11;:20;;;;;;;;;;;;;;;779:200::o;169:656:1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;169:656:1;;;-1:-1:-1;169:656:1;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;", + "deployedSourceMap": "169:656:1:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;169:656:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;644:81:86;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;644:81:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2453:145:84;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;2453:145:84;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;1514:89;;;:::i;:::-;;;;;;;;;;;;;;;;3055:252;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;3055:252:84;;;;;;;;;;;;;;;;;:::i;1478:81:86:-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;3702:203:84;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;3702:203:84;;;;;;;;:::i;656:166:1:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;656:166:1;;;;;;;;:::i;464:79::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;464:79:1;;:::i;:::-;;1661:108:84;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;1661:108:84;-1:-1:-1;;;;;1661:108:84;;:::i;549:101:1:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;549:101:1;;;;;;;;:::i;838:85:86:-;;;:::i;560:90:79:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;560:90:79;-1:-1:-1;;;;;560:90:79;;:::i;656:75::-;;;:::i;4392:213:84:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;4392:213:84;;;;;;;;:::i;1972:153::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;1972:153:84;;;;;;;;:::i;447:107:79:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;447:107:79;-1:-1:-1;;;;;447:107:79;;:::i;2183:132:84:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;2183:132:84;;;;;;;;;;:::i;644:81:86:-;713:5;706:12;;;;;;;;-1:-1:-1;;706:12:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;681:13;;706:12;;713:5;;706:12;;713:5;706:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;644:81;:::o;2453:145:84:-;2518:4;2534:36;2543:10;2555:7;2564:5;2534:8;:36::i;:::-;-1:-1:-1;2587:4:84;2453:145;;;;:::o;1514:89::-;1584:12;;1514:89;:::o;3055:252::-;3144:4;3160:36;3170:6;3178:9;3189:6;3160:9;:36::i;:::-;-1:-1:-1;;;;;3235:19:84;;;;;;:11;:19;;;;;;;;3223:10;3235:31;;;;;;;;;3206:73;;3215:6;;3235:43;;3271:6;3235:43;:35;:43;:::i;:::-;3206:8;:73::i;:::-;-1:-1:-1;3296:4:84;3055:252;;;;;:::o;1478:81:86:-;1543:9;;;;1478:81;:::o;3702:203:84:-;3807:10;3782:4;3828:23;;;:11;:23;;;;;;;;-1:-1:-1;;;;;3828:32:84;;;;;;;;;;3782:4;;3798:79;;3819:7;;3828:48;;3865:10;3828:48;:36;:48;:::i;656:166:1:-;753:4;350:20:79;359:10;350:8;:20::i;:::-;342:81;;;;-1:-1:-1;;;;;342:81:79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;773:21:1;779:7;788:5;773;:21::i;464:79::-;511:25;517:10;529:6;511:5;:25::i;:::-;464:79;:::o;1661:108:84:-;-1:-1:-1;;;;;1744:18:84;1718:7;1744:18;;;:9;:18;;;;;;;1661:108::o;549:101:1:-;617:26;627:7;636:6;617:9;:26::i;:::-;549:101;;:::o;838:85:86:-;909:7;902:14;;;;;;;;-1:-1:-1;;902:14:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;877:13;;902:14;;909:7;;902:14;;909:7;902:14;;;;;;;;;;;;;;;;;;;;;;;;560:90:79;350:20;359:10;350:8;:20::i;:::-;342:81;;;;-1:-1:-1;;;;;342:81:79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;624:19;635:7;624:10;:19::i;656:75::-;699:25;713:10;699:13;:25::i;:::-;656:75::o;4392:213:84:-;4502:10;4477:4;4523:23;;;:11;:23;;;;;;;;-1:-1:-1;;;;;4523:32:84;;;;;;;;;;4477:4;;4493:84;;4514:7;;4523:53;;4560:15;4523:53;:36;:53;:::i;1972:153::-;2041:4;2057:40;2067:10;2079:9;2090:6;2057:9;:40::i;447:107:79:-;503:4;526:21;:8;539:7;526:21;:12;:21;:::i;:::-;519:28;447:107;-1:-1:-1;;447:107:79:o;2183:132:84:-;-1:-1:-1;;;;;2281:18:84;;;2255:7;2281:18;;;:11;:18;;;;;;;;:27;;;;;;;;;;;;;2183:132::o;7117:329::-;-1:-1:-1;;;;;7209:19:84;;7201:68;;;;-1:-1:-1;;;;;7201:68:84;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;7287:21:84;;7279:68;;;;-1:-1:-1;;;;;7279:68:84;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;7358:18:84;;;;;;;:11;:18;;;;;;;;:27;;;;;;;;;;;;;:35;;;7408:31;;;;;;;;;;;;;;;;;7117:329;;;:::o;5079:422::-;-1:-1:-1;;;;;5176:20:84;;5168:70;;;;-1:-1:-1;;;;;5168:70:84;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;5256:23:84;;5248:71;;;;-1:-1:-1;;;;;5248:71:84;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;5350:17:84;;;;;;:9;:17;;;;;;:29;;5372:6;5350:29;:21;:29;:::i;:::-;-1:-1:-1;;;;;5330:17:84;;;;;;;:9;:17;;;;;;:49;;;;5412:20;;;;;;;:32;;5437:6;5412:32;:24;:32;:::i;:::-;-1:-1:-1;;;;;5389:20:84;;;;;;;:9;:20;;;;;;;;;:55;;;;5459:35;;;;;;;5389:20;;5459:35;;;;;;;;;;;;;5079:422;;;:::o;1274:179:83:-;1332:7;1364:1;1359;:6;;1351:49;;;;;-1:-1:-1;;;;;1351:49:83;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1422:5:83;;;1274:179::o;834:176::-;892:7;923:5;;;946:6;;;;938:46;;;;;-1:-1:-1;;;;;938:46:83;;;;;;;;;;;;;;;;;;;;;;;;;;;;1002:1;834:176;-1:-1:-1;;;834:176:83:o;5771:302:84:-;-1:-1:-1;;;;;5846:21:84;;5838:65;;;;;-1:-1:-1;;;;;5838:65:84;;;;;;;;;;;;;;;;;;;;;;;;;;;;5929:12;;:24;;5946:6;5929:24;:16;:24;:::i;:::-;5914:12;:39;-1:-1:-1;;;;;5984:18:84;;;;;;:9;:18;;;;;;:30;;6007:6;5984:30;:22;:30;:::i;:::-;-1:-1:-1;;;;;5963:18:84;;;;;;:9;:18;;;;;;;;:51;;;;6029:37;;;;;;;5963:18;;;;6029:37;;;;;;;;;;5771:302;;:::o;6392:300::-;-1:-1:-1;;;;;6466:21:84;;6458:67;;;;-1:-1:-1;;;;;6458:67:84;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6551:12;;:23;;6568:5;6551:23;:16;:23;:::i;:::-;6536:12;:38;-1:-1:-1;;;;;6605:18:84;;;;;;:9;:18;;;;;;:29;;6628:5;6605:29;:22;:29;:::i;:::-;-1:-1:-1;;;;;6584:18:84;;;;;;:9;:18;;;;;;;;:50;;;;6649:36;;;;;;;6584:18;;6649:36;;;;;;;;;;;6392:300;;:::o;7623:185::-;7694:22;7700:7;7709:6;7694:5;:22::i;:::-;-1:-1:-1;;;;;7756:20:84;;;;;;:11;:20;;;;;;;;7744:10;7756:32;;;;;;;;;7726:75;;7735:7;;7756:44;;7793:6;7756:44;:36;:44;:::i;737:119:79:-;793:21;:8;806:7;793:21;:12;:21;:::i;:::-;829:20;;-1:-1:-1;;;;;829:20:79;;;;;;;;737:119;:::o;862:127::-;921:24;:8;937:7;921:24;:15;:24;:::i;:::-;960:22;;-1:-1:-1;;;;;960:22:79;;;;;;;;862:127;:::o;779:200:78:-;851:4;-1:-1:-1;;;;;875:21:78;;867:68;;;;-1:-1:-1;;;;;867:68:78;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;952:20:78;:11;:20;;;;;;;;;;;;;;;779:200::o;260:175::-;337:18;341:4;347:7;337:3;:18::i;:::-;336:19;328:63;;;;;-1:-1:-1;;;;;328:63:78;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;401:20:78;:11;:20;;;;;;;;;;;:27;;-1:-1:-1;;401:27:78;424:4;401:27;;;260:175::o;510:180::-;589:18;593:4;599:7;589:3;:18::i;:::-;581:64;;;;-1:-1:-1;;;;;581:64:78;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;655:20:78;678:5;655:20;;;;;;;;;;;:28;;-1:-1:-1;;655:28:78;;;510:180::o", + "source": "pragma solidity ^0.5.0;\n\nimport 'openzeppelin-solidity/contracts/token/ERC20/ERC20Capped.sol';\nimport 'openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol';\n\n\ncontract VASYA20 is ERC20Detailed, ERC20Mintable {\n\n constructor(\n string memory name,\n string memory symbol,\n uint8 decimals\n )\n ERC20Detailed(name, symbol, decimals)\n public\n\n {\n // solium-disable-previous-line no-empty-blocks\n }\n\n function burn(uint256 amount) public {\n _burn(msg.sender, amount);\n }\n\n function burnFrom(address account, uint256 amount) public {\n _burnFrom(account, amount);\n }\n\n function mint(address account, uint256 value)\n public\n onlyMinter\n returns (bool)\n {\n _mint(account, value);\n return true;\n }\n\n}", + "sourcePath": "/home/elvis/work/d2/erc721/contracts/VASYA20.sol", + "ast": { + "absolutePath": "/home/elvis/work/d2/erc721/contracts/VASYA20.sol", + "exportedSymbols": { + "VASYA20": [ + 124 + ] + }, + "id": 125, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 58, + "literals": [ + "solidity", + "^", + "0.5", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "0:23:1" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/ERC20Capped.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/ERC20Capped.sol", + "id": 59, + "nodeType": "ImportDirective", + "scope": 125, + "sourceUnit": 9806, + "src": "25:69:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol", + "id": 60, + "nodeType": "ImportDirective", + "scope": 125, + "sourceUnit": 9864, + "src": "95:71:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 61, + "name": "ERC20Detailed", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 9863, + "src": "189:13:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20Detailed_$9863", + "typeString": "contract ERC20Detailed" + } + }, + "id": 62, + "nodeType": "InheritanceSpecifier", + "src": "189:13:1" + }, + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 63, + "name": "ERC20Mintable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 9891, + "src": "204:13:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20Mintable_$9891", + "typeString": "contract ERC20Mintable" + } + }, + "id": 64, + "nodeType": "InheritanceSpecifier", + "src": "204:13:1" + } + ], + "contractDependencies": [ + 9105, + 9746, + 9863, + 9891, + 9960 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 124, + "linearizedBaseContracts": [ + 124, + 9891, + 9105, + 9746, + 9863, + 9960 + ], + "name": "VASYA20", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": { + "id": 78, + "nodeType": "Block", + "src": "395:63:1", + "statements": [] + }, + "documentation": null, + "id": 79, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "id": 73, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 66, + "src": "351:4:1", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + { + "argumentTypes": null, + "id": 74, + "name": "symbol", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 68, + "src": "357:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + { + "argumentTypes": null, + "id": 75, + "name": "decimals", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 70, + "src": "365:8:1", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + } + ], + "id": 76, + "modifierName": { + "argumentTypes": null, + "id": 72, + "name": "ERC20Detailed", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9863, + "src": "337:13:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ERC20Detailed_$9863_$", + "typeString": "type(contract ERC20Detailed)" + } + }, + "nodeType": "ModifierInvocation", + "src": "337:37:1" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 71, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 66, + "name": "name", + "nodeType": "VariableDeclaration", + "scope": 79, + "src": "246:18:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 65, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "246:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 68, + "name": "symbol", + "nodeType": "VariableDeclaration", + "scope": 79, + "src": "274:20:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 67, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "274:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 70, + "name": "decimals", + "nodeType": "VariableDeclaration", + "scope": 79, + "src": "304:14:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 69, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "304:5:1", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "236:92:1" + }, + "returnParameters": { + "id": 77, + "nodeType": "ParameterList", + "parameters": [], + "src": "395:0:1" + }, + "scope": 124, + "src": "225:233:1", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 90, + "nodeType": "Block", + "src": "501:42:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 85, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 11255, + "src": "517:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 86, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "517:10:1", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 87, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 81, + "src": "529:6:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 84, + "name": "_burn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9675, + "src": "511:5:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 88, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "511:25:1", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 89, + "nodeType": "ExpressionStatement", + "src": "511:25:1" + } + ] + }, + "documentation": null, + "id": 91, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "burn", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 82, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 81, + "name": "amount", + "nodeType": "VariableDeclaration", + "scope": 91, + "src": "478:14:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 80, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "478:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "477:16:1" + }, + "returnParameters": { + "id": 83, + "nodeType": "ParameterList", + "parameters": [], + "src": "501:0:1" + }, + "scope": 124, + "src": "464:79:1", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 103, + "nodeType": "Block", + "src": "607:43:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 99, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 93, + "src": "627:7:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 100, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 95, + "src": "636:6:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 98, + "name": "_burnFrom", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9745, + "src": "617:9:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 101, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "617:26:1", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 102, + "nodeType": "ExpressionStatement", + "src": "617:26:1" + } + ] + }, + "documentation": null, + "id": 104, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "burnFrom", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 96, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 93, + "name": "account", + "nodeType": "VariableDeclaration", + "scope": 104, + "src": "567:15:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 92, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "567:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 95, + "name": "amount", + "nodeType": "VariableDeclaration", + "scope": 104, + "src": "584:14:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 94, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "584:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "566:33:1" + }, + "returnParameters": { + "id": 97, + "nodeType": "ParameterList", + "parameters": [], + "src": "607:0:1" + }, + "scope": 124, + "src": "549:101:1", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 122, + "nodeType": "Block", + "src": "763:59:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 116, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 106, + "src": "779:7:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 117, + "name": "value", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 108, + "src": "788:5:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 115, + "name": "_mint", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9632, + "src": "773:5:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 118, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "773:21:1", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 119, + "nodeType": "ExpressionStatement", + "src": "773:21:1" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 120, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "811:4:1", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 114, + "id": 121, + "nodeType": "Return", + "src": "804:11:1" + } + ] + }, + "documentation": null, + "id": 123, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 111, + "modifierName": { + "argumentTypes": null, + "id": 110, + "name": "onlyMinter", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9038, + "src": "725:10:1", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "725:10:1" + } + ], + "name": "mint", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 109, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 106, + "name": "account", + "nodeType": "VariableDeclaration", + "scope": 123, + "src": "670:15:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 105, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "670:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 108, + "name": "value", + "nodeType": "VariableDeclaration", + "scope": 123, + "src": "687:13:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 107, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "687:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "669:32:1" + }, + "returnParameters": { + "id": 114, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 113, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 123, + "src": "753:4:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 112, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "753:4:1", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "752:6:1" + }, + "scope": 124, + "src": "656:166:1", + "stateMutability": "nonpayable", + "superFunction": 9890, + "visibility": "public" + } + ], + "scope": 125, + "src": "169:656:1" + } + ], + "src": "0:825:1" + }, + "legacyAST": { + "absolutePath": "/home/elvis/work/d2/erc721/contracts/VASYA20.sol", + "exportedSymbols": { + "VASYA20": [ + 124 + ] + }, + "id": 125, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 58, + "literals": [ + "solidity", + "^", + "0.5", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "0:23:1" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/ERC20Capped.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/ERC20Capped.sol", + "id": 59, + "nodeType": "ImportDirective", + "scope": 125, + "sourceUnit": 9806, + "src": "25:69:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol", + "id": 60, + "nodeType": "ImportDirective", + "scope": 125, + "sourceUnit": 9864, + "src": "95:71:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 61, + "name": "ERC20Detailed", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 9863, + "src": "189:13:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20Detailed_$9863", + "typeString": "contract ERC20Detailed" + } + }, + "id": 62, + "nodeType": "InheritanceSpecifier", + "src": "189:13:1" + }, + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 63, + "name": "ERC20Mintable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 9891, + "src": "204:13:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20Mintable_$9891", + "typeString": "contract ERC20Mintable" + } + }, + "id": 64, + "nodeType": "InheritanceSpecifier", + "src": "204:13:1" + } + ], + "contractDependencies": [ + 9105, + 9746, + 9863, + 9891, + 9960 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 124, + "linearizedBaseContracts": [ + 124, + 9891, + 9105, + 9746, + 9863, + 9960 + ], + "name": "VASYA20", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": { + "id": 78, + "nodeType": "Block", + "src": "395:63:1", + "statements": [] + }, + "documentation": null, + "id": 79, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "id": 73, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 66, + "src": "351:4:1", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + { + "argumentTypes": null, + "id": 74, + "name": "symbol", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 68, + "src": "357:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + { + "argumentTypes": null, + "id": 75, + "name": "decimals", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 70, + "src": "365:8:1", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + } + ], + "id": 76, + "modifierName": { + "argumentTypes": null, + "id": 72, + "name": "ERC20Detailed", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9863, + "src": "337:13:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ERC20Detailed_$9863_$", + "typeString": "type(contract ERC20Detailed)" + } + }, + "nodeType": "ModifierInvocation", + "src": "337:37:1" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 71, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 66, + "name": "name", + "nodeType": "VariableDeclaration", + "scope": 79, + "src": "246:18:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 65, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "246:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 68, + "name": "symbol", + "nodeType": "VariableDeclaration", + "scope": 79, + "src": "274:20:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 67, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "274:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 70, + "name": "decimals", + "nodeType": "VariableDeclaration", + "scope": 79, + "src": "304:14:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 69, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "304:5:1", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "236:92:1" + }, + "returnParameters": { + "id": 77, + "nodeType": "ParameterList", + "parameters": [], + "src": "395:0:1" + }, + "scope": 124, + "src": "225:233:1", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 90, + "nodeType": "Block", + "src": "501:42:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 85, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 11255, + "src": "517:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 86, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "517:10:1", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 87, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 81, + "src": "529:6:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 84, + "name": "_burn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9675, + "src": "511:5:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 88, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "511:25:1", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 89, + "nodeType": "ExpressionStatement", + "src": "511:25:1" + } + ] + }, + "documentation": null, + "id": 91, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "burn", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 82, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 81, + "name": "amount", + "nodeType": "VariableDeclaration", + "scope": 91, + "src": "478:14:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 80, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "478:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "477:16:1" + }, + "returnParameters": { + "id": 83, + "nodeType": "ParameterList", + "parameters": [], + "src": "501:0:1" + }, + "scope": 124, + "src": "464:79:1", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 103, + "nodeType": "Block", + "src": "607:43:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 99, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 93, + "src": "627:7:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 100, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 95, + "src": "636:6:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 98, + "name": "_burnFrom", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9745, + "src": "617:9:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 101, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "617:26:1", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 102, + "nodeType": "ExpressionStatement", + "src": "617:26:1" + } + ] + }, + "documentation": null, + "id": 104, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "burnFrom", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 96, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 93, + "name": "account", + "nodeType": "VariableDeclaration", + "scope": 104, + "src": "567:15:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 92, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "567:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 95, + "name": "amount", + "nodeType": "VariableDeclaration", + "scope": 104, + "src": "584:14:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 94, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "584:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "566:33:1" + }, + "returnParameters": { + "id": 97, + "nodeType": "ParameterList", + "parameters": [], + "src": "607:0:1" + }, + "scope": 124, + "src": "549:101:1", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 122, + "nodeType": "Block", + "src": "763:59:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 116, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 106, + "src": "779:7:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 117, + "name": "value", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 108, + "src": "788:5:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 115, + "name": "_mint", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9632, + "src": "773:5:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 118, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "773:21:1", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 119, + "nodeType": "ExpressionStatement", + "src": "773:21:1" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 120, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "811:4:1", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 114, + "id": 121, + "nodeType": "Return", + "src": "804:11:1" + } + ] + }, + "documentation": null, + "id": 123, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 111, + "modifierName": { + "argumentTypes": null, + "id": 110, + "name": "onlyMinter", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9038, + "src": "725:10:1", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "725:10:1" + } + ], + "name": "mint", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 109, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 106, + "name": "account", + "nodeType": "VariableDeclaration", + "scope": 123, + "src": "670:15:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 105, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "670:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 108, + "name": "value", + "nodeType": "VariableDeclaration", + "scope": 123, + "src": "687:13:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 107, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "687:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "669:32:1" + }, + "returnParameters": { + "id": 114, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 113, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 123, + "src": "753:4:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 112, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "753:4:1", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "752:6:1" + }, + "scope": 124, + "src": "656:166:1", + "stateMutability": "nonpayable", + "superFunction": 9890, + "visibility": "public" + } + ], + "scope": 125, + "src": "169:656:1" + } + ], + "src": "0:825:1" + }, + "compiler": { + "name": "solc", + "version": "0.5.11+commit.c082d0b4.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.0.11", + "updatedAt": "2019-09-06T10:41:38.931Z", + "devdoc": { + "methods": { + "allowance(address,address)": { + "details": "See `IERC20.allowance`." + }, + "approve(address,uint256)": { + "details": "See `IERC20.approve`. * Requirements: * - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "See `IERC20.balanceOf`." + }, + "decimals()": { + "details": "Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5,05` (`505 / 10 ** 2`). * Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. * > Note that this information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including `IERC20.balanceOf` and `IERC20.transfer`." + }, + "decreaseAllowance(address,uint256)": { + "details": "Atomically decreases the allowance granted to `spender` by the caller. * This is an alternative to `approve` that can be used as a mitigation for problems described in `IERC20.approve`. * Emits an `Approval` event indicating the updated allowance. * Requirements: * - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`." + }, + "increaseAllowance(address,uint256)": { + "details": "Atomically increases the allowance granted to `spender` by the caller. * This is an alternative to `approve` that can be used as a mitigation for problems described in `IERC20.approve`. * Emits an `Approval` event indicating the updated allowance. * Requirements: * - `spender` cannot be the zero address." + }, + "name()": { + "details": "Returns the name of the token." + }, + "symbol()": { + "details": "Returns the symbol of the token, usually a shorter version of the name." + }, + "totalSupply()": { + "details": "See `IERC20.totalSupply`." + }, + "transfer(address,uint256)": { + "details": "See `IERC20.transfer`. * Requirements: * - `recipient` cannot be the zero address. - the caller must have a balance of at least `amount`." + }, + "transferFrom(address,address,uint256)": { + "details": "See `IERC20.transferFrom`. * Emits an `Approval` event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of `ERC20`; * Requirements: - `sender` and `recipient` cannot be the zero address. - `sender` must have a balance of at least `value`. - the caller must have allowance for `sender`'s tokens of at least `amount`." + } + } + }, + "userdoc": { + "methods": {} + } +} diff --git a/test/resources/ERC721FullMetadataMintable.json b/test/resources/ERC721FullMetadataMintable.json new file mode 100644 index 00000000..f7de52a0 --- /dev/null +++ b/test/resources/ERC721FullMetadataMintable.json @@ -0,0 +1,3099 @@ +{ + "contractName": "VASYA721", + "abi": [ + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "tokenURI", + "type": "string" + } + ], + "name": "mintWithTokenURI", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "addMinter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceMinter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "isMinter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MinterAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MinterRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "tokenURI", + "type": "string" + } + ], + "name": "setTokenURI", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.5.11+commit.c082d0b4\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"getApproved\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"tokenURI\",\"type\":\"string\"}],\"name\":\"setTokenURI\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"tokenOfOwnerByIndex\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"tokenByIndex\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"tokenURI\",\"type\":\"string\"}],\"name\":\"mintWithTokenURI\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"ownerOf\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"addMinter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"renounceMinter\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"setApprovalForAll\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"isMinter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"tokenURI\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"isApprovedForAll\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"symbol\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"MinterAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"MinterRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"approved\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"ApprovalForAll\",\"type\":\"event\"}],\"devdoc\":{\"methods\":{\"approve(address,uint256)\":{\"details\":\"Approves another address to transfer the given token ID The zero address indicates there is no approved address. There can only be one approved address per token at a given time. Can only be called by the token owner or an approved operator.\",\"params\":{\"to\":\"address to be approved for the given token ID\",\"tokenId\":\"uint256 ID of the token to be approved\"}},\"balanceOf(address)\":{\"details\":\"Gets the balance of the specified address.\",\"params\":{\"owner\":\"address to query the balance of\"},\"return\":\"uint256 representing the amount owned by the passed address\"},\"getApproved(uint256)\":{\"details\":\"Gets the approved address for a token ID, or zero if no address set Reverts if the token ID does not exist.\",\"params\":{\"tokenId\":\"uint256 ID of the token to query the approval of\"},\"return\":\"address currently approved for the given token ID\"},\"isApprovedForAll(address,address)\":{\"details\":\"Tells whether an operator is approved by a given owner.\",\"params\":{\"operator\":\"operator address which you want to query the approval of\",\"owner\":\"owner address which you want to query the approval of\"},\"return\":\"bool whether the given operator is approved by the given owner\"},\"mintWithTokenURI(address,uint256,string)\":{\"details\":\"Function to mint tokens.\",\"params\":{\"to\":\"The address that will receive the minted tokens.\",\"tokenId\":\"The token id to mint.\",\"tokenURI\":\"The token URI of the minted token.\"},\"return\":\"A boolean that indicates if the operation was successful.\"},\"name()\":{\"details\":\"Gets the token name.\",\"return\":\"string representing the token name\"},\"ownerOf(uint256)\":{\"details\":\"Gets the owner of the specified token ID.\",\"params\":{\"tokenId\":\"uint256 ID of the token to query the owner of\"},\"return\":\"address currently marked as the owner of the given token ID\"},\"safeTransferFrom(address,address,uint256)\":{\"details\":\"Safely transfers the ownership of a given token ID to another address If the target address is a contract, it must implement `onERC721Received`, which is called upon a safe transfer, and return the magic value `bytes4(keccak256(\\\"onERC721Received(address,address,uint256,bytes)\\\"))`; otherwise, the transfer is reverted. Requires the msg.sender to be the owner, approved, or operator\",\"params\":{\"from\":\"current owner of the token\",\"to\":\"address to receive the ownership of the given token ID\",\"tokenId\":\"uint256 ID of the token to be transferred\"}},\"safeTransferFrom(address,address,uint256,bytes)\":{\"details\":\"Safely transfers the ownership of a given token ID to another address If the target address is a contract, it must implement `onERC721Received`, which is called upon a safe transfer, and return the magic value `bytes4(keccak256(\\\"onERC721Received(address,address,uint256,bytes)\\\"))`; otherwise, the transfer is reverted. Requires the msg.sender to be the owner, approved, or operator\",\"params\":{\"_data\":\"bytes data to send along with a safe transfer check\",\"from\":\"current owner of the token\",\"to\":\"address to receive the ownership of the given token ID\",\"tokenId\":\"uint256 ID of the token to be transferred\"}},\"setApprovalForAll(address,bool)\":{\"details\":\"Sets or unsets the approval of a given operator An operator is allowed to transfer all tokens of the sender on their behalf.\",\"params\":{\"approved\":\"representing the status of the approval to be set\",\"to\":\"operator address to set the approval\"}},\"supportsInterface(bytes4)\":{\"details\":\"See `IERC165.supportsInterface`. * Time complexity O(1), guaranteed to always use less than 30 000 gas.\"},\"symbol()\":{\"details\":\"Gets the token symbol.\",\"return\":\"string representing the token symbol\"},\"tokenByIndex(uint256)\":{\"details\":\"Gets the token ID at a given index of all the tokens in this contract Reverts if the index is greater or equal to the total number of tokens.\",\"params\":{\"index\":\"uint256 representing the index to be accessed of the tokens list\"},\"return\":\"uint256 token ID at the given index of the tokens list\"},\"tokenOfOwnerByIndex(address,uint256)\":{\"details\":\"Gets the token ID at a given index of the tokens list of the requested owner.\",\"params\":{\"index\":\"uint256 representing the index to be accessed of the requested tokens list\",\"owner\":\"address owning the tokens list to be accessed\"},\"return\":\"uint256 token ID at the given index of the tokens list owned by the requested address\"},\"tokenURI(uint256)\":{\"details\":\"Returns an URI for a given token ID. Throws if the token ID does not exist. May return an empty string.\",\"params\":{\"tokenId\":\"uint256 ID of the token to query\"}},\"totalSupply()\":{\"details\":\"Gets the total amount of tokens stored by the contract.\",\"return\":\"uint256 representing the total amount of tokens\"},\"transferFrom(address,address,uint256)\":{\"details\":\"Transfers the ownership of a given token ID to another address. Usage of this method is discouraged, use `safeTransferFrom` whenever possible. Requires the msg.sender to be the owner, approved, or operator.\",\"params\":{\"from\":\"current owner of the token\",\"to\":\"address to receive the ownership of the given token ID\",\"tokenId\":\"uint256 ID of the token to be transferred\"}}}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/home/elvis/work/d2/erc721/contracts/VASYA721.sol\":\"VASYA721\"},\"evmVersion\":\"byzantium\",\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/elvis/work/d2/erc721/contracts/VASYA721.sol\":{\"keccak256\":\"0x6504ae3ab8054868f4516fc9564eb728a55ec7440ec9dfd7a967cbd73cc9cd23\",\"urls\":[\"bzz-raw://5580fd179011de429fbb0ba5c7d98e19dfe090a625c8b103ecf30b79efeabd6a\",\"dweb:/ipfs/QmNY3hACV816za6YZ1KVKXjyTJJupMg7vUT7FyWznC7Rw3\"]},\"openzeppelin-solidity/contracts/access/Roles.sol\":{\"keccak256\":\"0xb002c378d7b82a101bd659c341518953ca0919d342c0a400196982c0e7e7bcdb\",\"urls\":[\"bzz-raw://00a788c4631466c220b385bdd100c571d24b2deccd657615cfbcef6cadf669a4\",\"dweb:/ipfs/QmTEwDbjJNxmMNCDMqtuou3dyM8Wtp8Q9NFvn7SAVM7Jf3\"]},\"openzeppelin-solidity/contracts/access/roles/MinterRole.sol\":{\"keccak256\":\"0x63da54a7a5d4e4d82ac8d1f4f7f995fd8ef2b5fe1f2960b83e534fa37fb60910\",\"urls\":[\"bzz-raw://22e54aa25d633d51efaadb0c956ddd80616a79ee79a41bb8d958473712732612\",\"dweb:/ipfs/QmNbiwWkL4v1i6TgrppGahxHBUHJUrLJLVc8EDb7DFsVDq\"]},\"openzeppelin-solidity/contracts/drafts/Counters.sol\":{\"keccak256\":\"0x5eb69360d3441ab2ee799bd7c007cccbffb0896f12b2dfe1456193e2aa180a11\",\"urls\":[\"bzz-raw://072e8b5a1b5acfc2acba9b6fb87d1dc57065aad44572617ad46b014074969eb3\",\"dweb:/ipfs/QmVTDHUriaxBJqiWCWgWC8vYaYYfXSz883LsowzTx1DcpK\"]},\"openzeppelin-solidity/contracts/introspection/ERC165.sol\":{\"keccak256\":\"0xac2eacd7e7762e275442f28f21d821544df5aae2ed7698af13be8c41b7005e2e\",\"urls\":[\"bzz-raw://8bdbefb642e7b08535c66bbf074e576cfef2300cdf910c1e0b211f6393833a28\",\"dweb:/ipfs/QmQhfx2Ufr8a2gFXm3KogL66xGgAuAWMwcamkWFKGG6Vya\"]},\"openzeppelin-solidity/contracts/introspection/IERC165.sol\":{\"keccak256\":\"0x661553e43d7c4fbb2de504e5999fd5c104d367488350ed5bf023031bd1ba5ac5\",\"urls\":[\"bzz-raw://b40442c5b350b57b88a081a1eacd2bac962d4ecc1f029f5447a18986f08f6f14\",\"dweb:/ipfs/QmV7wjtRf11ibUHh4g8JjuhMpy68pPhV95L2y46UBoRfsZ\"]},\"openzeppelin-solidity/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x4ccf2d7b51873db1ccfd54ca2adae5eac3b184f9699911ed4490438419f1c690\",\"urls\":[\"bzz-raw://d62d769b2219d5de39013093412623e624fa887f871826ea3bae6052ee893610\",\"dweb:/ipfs/QmV3yVktya1s617QmuzQR2CfuJgUi3dR2xEZY9ecmqZ2G1\"]},\"openzeppelin-solidity/contracts/token/ERC721/ERC721.sol\":{\"keccak256\":\"0xf151df411bbf4eaef1fc8e8480cd10c2cd985f1a36517e63981517610213efc1\",\"urls\":[\"bzz-raw://2a277b3cc3a1a03af5c039bc75cd16bb2d23b529cb2564cc0bea1b1e0eb4dd68\",\"dweb:/ipfs/QmNRxggY8qmjjuLnxggRqE8uBueuCktLwi9YYHagWoaEJ8\"]},\"openzeppelin-solidity/contracts/token/ERC721/ERC721Enumerable.sol\":{\"keccak256\":\"0xb42cb5a7471d98114af8f7050e7d08dfd543f432713a8aee72a45ce9485f5bed\",\"urls\":[\"bzz-raw://324b8d35f37d91fd863130a6e4b83d55b8b95264af011a008ec03e084ac91b75\",\"dweb:/ipfs/QmWY61QSSJPkgDb2WZR1unTfuSrd5jBp2tC7RrCrQxC9CL\"]},\"openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol\":{\"keccak256\":\"0x908d0d6fd8d30cffb1fe9fce1f7db6802cb885fbe88d2f170539e8dcc6afa946\",\"urls\":[\"bzz-raw://ed5f7883e2a50b3efeaae764c77152bef9d3d9965832ef095c94e79c2e887257\",\"dweb:/ipfs/QmaLRQ1ziLAo18gvM2EbKhvHC3ys4333mTxCFfoEJ8AodY\"]},\"openzeppelin-solidity/contracts/token/ERC721/ERC721Metadata.sol\":{\"keccak256\":\"0xe5e28b1a405164faa98f8ecd8ed2b00907b89e3d58b2b2bb8ac8b12bc5714489\",\"urls\":[\"bzz-raw://0d5d3f0caa7e7ec91f8a2894e1a6a3513a0c79aa91a498ebf8fdbdd07c12286f\",\"dweb:/ipfs/QmP7r4jQMRxXb5JHy5V9bgMz5FmTezcSDd7ivyzJN88pTR\"]},\"openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol\":{\"keccak256\":\"0xbd9b003914fb0188bfeb7a97ae50c1d02093ba58a9c197dd80b667bf2c2c6f5c\",\"urls\":[\"bzz-raw://2efc0a6ad7c4a19986a34b963d39cf86778814c08aa65685b47c94f0a88b33fc\",\"dweb:/ipfs/QmdZp8fsSgC2ULMEweP2rGmRcjLZHohBZerQgxnPYWz78W\"]},\"openzeppelin-solidity/contracts/token/ERC721/IERC721.sol\":{\"keccak256\":\"0xce48937a8007c580b8e2e35705738c922dd17540de89ebee6df06d2917a3f9fc\",\"urls\":[\"bzz-raw://1d117265103ee3efcd454d3aafb3e79a115f9bca6dec78a1229558eb30d14d05\",\"dweb:/ipfs/QmTm5Z1c7zzPiG3Cfj1eBMB23AeiEFGgvmTFQVaeEWXVCw\"]},\"openzeppelin-solidity/contracts/token/ERC721/IERC721Enumerable.sol\":{\"keccak256\":\"0x5c573acd6c7b98d71f3c1c44d63dc17400b0fd7b26a52c9fded43b8b533dc4ec\",\"urls\":[\"bzz-raw://e23af54444d2dbfae58895339eb7b189e1ba5d0b7abde63716e7ef7da23b2fde\",\"dweb:/ipfs/QmZva7dE1SMd1yyizzc6ivSoBXXwpNveLV7iFNATNpq68Y\"]},\"openzeppelin-solidity/contracts/token/ERC721/IERC721Metadata.sol\":{\"keccak256\":\"0x2b2b99dc7fe8fcd1f9427d00822b99cbc683dc21f5dd7532bd7e2281fd2c2ca2\",\"urls\":[\"bzz-raw://a8024c00e34efaf328f9592e76823c79f25fa0f6006bdf4a1e7fea204afd4773\",\"dweb:/ipfs/QmZns9jTr7843njq3J2iL2LLoWXK5mdzN1bDGd9GL3ahhD\"]},\"openzeppelin-solidity/contracts/token/ERC721/IERC721Receiver.sol\":{\"keccak256\":\"0xadbfb7028fb0f851dc848a48b9e54e7c89ffd2c2edc12fa4ba9bb383dfaa83d9\",\"urls\":[\"bzz-raw://90dceab42713246639100b87d6650037d68e4a2ab2dd4b5768c3ed35d6b3a4a0\",\"dweb:/ipfs/QmQ42UW5nchMoYP9bU9F1AJga5chG8j92fCPkURpiDKsCu\"]},\"openzeppelin-solidity/contracts/utils/Address.sol\":{\"keccak256\":\"0xf3358e5819ca73357abd6c90bdfffd0474af54364897f6b3e3234c4b71fbe9a1\",\"urls\":[\"bzz-raw://75ae8d04454d1511a2ed986cc8585736f05c5c25280683b3d24712a9f414a4bf\",\"dweb:/ipfs/Qmb3kNCoBUZdah1AgBBD4zMk898j5Qw8ahT1w5cCMYp5Y3\"]}},\"version\":1}", + "bytecode": "0x60806040523480156200001157600080fd5b50604051620025d9380380620025d9833981810160405260408110156200003757600080fd5b81019080805160405193929190846401000000008211156200005857600080fd5b9083019060208201858111156200006e57600080fd5b82516401000000008111828201881017156200008957600080fd5b82525081516020918201929091019080838360005b83811015620000b85781810151838201526020016200009e565b50505050905090810190601f168015620000e65780820380516001836020036101000a031916815260200191505b50604052602001805160405193929190846401000000008211156200010a57600080fd5b9083019060208201858111156200012057600080fd5b82516401000000008111828201881017156200013b57600080fd5b82525081516020918201929091019080838360005b838110156200016a57818101518382015260200162000150565b50505050905090810190601f168015620001985780820380516001836020036101000a031916815260200191505b50604052508391508290508181620001d97f01ffc9a700000000000000000000000000000000000000000000000000000000640100000000620002c7810204565b6200020d7f80ac58cd00000000000000000000000000000000000000000000000000000000640100000000620002c7810204565b620002417f780e9d6300000000000000000000000000000000000000000000000000000000640100000000620002c7810204565b81516200025690600990602085019062000512565b5080516200026c90600a90602084019062000512565b50620002a17f5b5e139f00000000000000000000000000000000000000000000000000000000640100000000620002c7810204565b50505050620002bf3362000396640100000000026401000000009004565b5050620005b7565b7fffffffff0000000000000000000000000000000000000000000000000000000080821614156200035957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4552433136353a20696e76616c696420696e7465726661636520696400000000604482015290519081900360640190fd5b7fffffffff00000000000000000000000000000000000000000000000000000000166000908152602081905260409020805460ff19166001179055565b620003b1600c8264010000000062001826620003e882021704565b604051600160a060020a038216907f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f690600090a250565b620003fd82826401000000006200048f810204565b156200046a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f526f6c65733a206163636f756e7420616c72656164792068617320726f6c6500604482015290519081900360640190fd5b600160a060020a0316600090815260209190915260409020805460ff19166001179055565b6000600160a060020a038216620004f2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180620025b76022913960400191505060405180910390fd5b50600160a060020a03166000908152602091909152604090205460ff1690565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200055557805160ff191683800117855562000585565b8280016001018555821562000585579182015b828111156200058557825182559160200191906001019062000568565b506200059392915062000597565b5090565b620005b491905b808211156200059357600081556001016200059e565b90565b611ff080620005c76000396000f3fe608060405234801561001057600080fd5b506004361061016a576000357c0100000000000000000000000000000000000000000000000000000000900480634f6ccce7116100e057806398650275116100995780639865027514610594578063a22cb4651461059c578063aa271e1a146105ca578063b88d4fde146105f0578063c87b56dd146106b6578063e985e9c5146106d35761016a565b80634f6ccce71461044b57806350bb4e7f146104685780636352211e1461052357806370a082311461054057806395d89b4114610566578063983b2d561461056e5761016a565b806318160ddd1161013257806318160ddd1461035057806323b872dd1461036a5780632f745c59146103a057806340c10f19146103cc57806342842e0e146103f857806342966c681461042e5761016a565b806301ffc9a71461016f57806306fdde03146101bf578063081812fc1461023c578063095ea7b314610275578063162094c4146102a3575b600080fd5b6101ab6004803603602081101561018557600080fd5b50357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916610701565b604080519115158252519081900360200190f35b6101c7610735565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102015781810151838201526020016101e9565b50505050905090810190601f16801561022e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102596004803603602081101561025257600080fd5b50356107cc565b60408051600160a060020a039092168252519081900360200190f35b6102a16004803603604081101561028b57600080fd5b50600160a060020a038135169060200135610831565b005b6101ab600480360360408110156102b957600080fd5b813591908101906040810160208201356401000000008111156102db57600080fd5b8201836020820111156102ed57600080fd5b8035906020019184600183028401116401000000008311171561030f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610955945050505050565b61035861098f565b60408051918252519081900360200190f35b6102a16004803603606081101561038057600080fd5b50600160a060020a03813581169160208101359091169060400135610995565b610358600480360360408110156103b657600080fd5b50600160a060020a0381351690602001356109ed565b6101ab600480360360408110156103e257600080fd5b50600160a060020a038135169060200135610a6f565b6102a16004803603606081101561040e57600080fd5b50600160a060020a03813581169160208101359091169060400135610ac2565b6102a16004803603602081101561044457600080fd5b5035610add565b6103586004803603602081101561046157600080fd5b5035610b31565b6101ab6004803603606081101561047e57600080fd5b600160a060020a03823516916020810135918101906060810160408201356401000000008111156104ae57600080fd5b8201836020820111156104c057600080fd5b803590602001918460018302840111640100000000831117156104e257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610b9a945050505050565b6102596004803603602081101561053957600080fd5b5035610c01565b6103586004803603602081101561055657600080fd5b5035600160a060020a0316610c5e565b6101c7610cc9565b6102a16004803603602081101561058457600080fd5b5035600160a060020a0316610d2a565b6102a1610d7a565b6102a1600480360360408110156105b257600080fd5b50600160a060020a0381351690602001351515610d85565b6101ab600480360360208110156105e057600080fd5b5035600160a060020a0316610e54565b6102a16004803603608081101561060657600080fd5b600160a060020a0382358116926020810135909116916040820135919081019060808101606082013564010000000081111561064157600080fd5b82018360208201111561065357600080fd5b8035906020019184600183028401116401000000008311171561067557600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610e67945050505050565b6101c7600480360360208110156106cc57600080fd5b5035610ec2565b6101ab600480360360408110156106e957600080fd5b50600160a060020a0381358116916020013516610faa565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191660009081526020819052604090205460ff1690565b60098054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156107c15780601f10610796576101008083540402835291602001916107c1565b820191906000526020600020905b8154815290600101906020018083116107a457829003601f168201915b505050505090505b90565b60006107d782610fd8565b6108155760405160e560020a62461bcd02815260040180806020018281038252602c815260200180611e17602c913960400191505060405180910390fd5b50600090815260026020526040902054600160a060020a031690565b600061083c82610c01565b905080600160a060020a031683600160a060020a031614156108925760405160e560020a62461bcd028152600401808060200182810382526021815260200180611ee96021913960400191505060405180910390fd5b33600160a060020a03821614806108ae57506108ae8133610faa565b6108ec5760405160e560020a62461bcd028152600401808060200182810382526038815260200180611d3b6038913960400191505060405180910390fd5b600082815260026020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0387811691821790925591518593918516917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a4505050565b600061096083610fd8565b61096957600080fd5b6109733384610ff5565b61097c57600080fd5b610986838361109c565b50600192915050565b60075490565b61099f3382610ff5565b6109dd5760405160e560020a62461bcd028152600401808060200182810382526031815260200180611f0a6031913960400191505060405180910390fd5b6109e8838383611102565b505050565b60006109f883610c5e565b8210610a385760405160e560020a62461bcd02815260040180806020018281038252602b815260200180611c8e602b913960400191505060405180910390fd5b600160a060020a0383166000908152600560205260409020805483908110610a5c57fe5b9060005260206000200154905092915050565b6000610a7a33610e54565b610ab85760405160e560020a62461bcd028152600401808060200182810382526030815260200180611dc66030913960400191505060405180910390fd5b6109868383611121565b6109e883838360405180602001604052806000815250610e67565b610ae73382610ff5565b610b255760405160e560020a62461bcd028152600401808060200182810382526030815260200180611f8c6030913960400191505060405180910390fd5b610b2e81611142565b50565b6000610b3b61098f565b8210610b7b5760405160e560020a62461bcd02815260040180806020018281038252602c815260200180611f3b602c913960400191505060405180910390fd5b60078281548110610b8857fe5b90600052602060002001549050919050565b6000610ba533610e54565b610be35760405160e560020a62461bcd028152600401808060200182810382526030815260200180611dc66030913960400191505060405180910390fd5b610bed8484611121565b610bf7838361109c565b5060019392505050565b600081815260016020526040812054600160a060020a031680610c585760405160e560020a62461bcd028152600401808060200182810382526029815260200180611d9d6029913960400191505060405180910390fd5b92915050565b6000600160a060020a038216610ca85760405160e560020a62461bcd02815260040180806020018281038252602a815260200180611d73602a913960400191505060405180910390fd5b600160a060020a0382166000908152600360205260409020610c5890611154565b600a8054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156107c15780601f10610796576101008083540402835291602001916107c1565b610d3333610e54565b610d715760405160e560020a62461bcd028152600401808060200182810382526030815260200180611dc66030913960400191505060405180910390fd5b610b2e81611158565b610d83336111a0565b565b600160a060020a038216331415610de6576040805160e560020a62461bcd02815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c657200000000000000604482015290519081900360640190fd5b336000818152600460209081526040808320600160a060020a03871680855290835292819020805460ff1916861515908117909155815190815290519293927f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31929181900390910190a35050565b6000610c58600c8363ffffffff6111e816565b610e72848484610995565b610e7e84848484611252565b610ebc5760405160e560020a62461bcd028152600401808060200182810382526032815260200180611cb96032913960400191505060405180910390fd5b50505050565b6060610ecd82610fd8565b610f0b5760405160e560020a62461bcd02815260040180806020018281038252602f815260200180611eba602f913960400191505060405180910390fd5b6000828152600b602090815260409182902080548351601f600260001961010060018616150201909316929092049182018490048402810184019094528084529091830182828015610f9e5780601f10610f7357610100808354040283529160200191610f9e565b820191906000526020600020905b815481529060010190602001808311610f8157829003601f168201915b50505050509050919050565b600160a060020a03918216600090815260046020908152604080832093909416825291909152205460ff1690565b600090815260016020526040902054600160a060020a0316151590565b600061100082610fd8565b61103e5760405160e560020a62461bcd02815260040180806020018281038252602c815260200180611d0f602c913960400191505060405180910390fd5b600061104983610c01565b905080600160a060020a031684600160a060020a03161480611084575083600160a060020a0316611079846107cc565b600160a060020a0316145b8061109457506110948185610faa565b949350505050565b6110a582610fd8565b6110e35760405160e560020a62461bcd02815260040180806020018281038252602c815260200180611e43602c913960400191505060405180910390fd5b6000828152600b6020908152604090912082516109e892840190611b91565b61110d8383836113cc565b6111178382611523565b6109e88282611618565b61112b8282611656565b6111358282611618565b61113e8161179a565b5050565b610b2e61114e82610c01565b826117de565b5490565b611169600c8263ffffffff61182616565b604051600160a060020a038216907f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f690600090a250565b6111b1600c8263ffffffff6118aa16565b604051600160a060020a038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669290600090a250565b6000600160a060020a0382166112325760405160e560020a62461bcd028152600401808060200182810382526022815260200180611e6f6022913960400191505060405180910390fd5b50600160a060020a03166000908152602091909152604090205460ff1690565b600061126684600160a060020a0316611914565b61127257506001611094565b6040517f150b7a020000000000000000000000000000000000000000000000000000000081523360048201818152600160a060020a03888116602485015260448401879052608060648501908152865160848601528651600095928a169463150b7a029490938c938b938b939260a4019060208501908083838e5b838110156113055781810151838201526020016112ed565b50505050905090810190601f1680156113325780820380516001836020036101000a031916815260200191505b5095505050505050602060405180830381600087803b15801561135457600080fd5b505af1158015611368573d6000803e3d6000fd5b505050506040513d602081101561137e57600080fd5b50517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f150b7a020000000000000000000000000000000000000000000000000000000014915050949350505050565b82600160a060020a03166113df82610c01565b600160a060020a0316146114275760405160e560020a62461bcd028152600401808060200182810382526029815260200180611e916029913960400191505060405180910390fd5b600160a060020a03821661146f5760405160e560020a62461bcd028152600401808060200182810382526024815260200180611ceb6024913960400191505060405180910390fd5b6114788161191a565b600160a060020a038316600090815260036020526040902061149990611962565b600160a060020a03821660009081526003602052604090206114ba90611979565b600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0386811691821790925591518493918716917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b600160a060020a03821660009081526005602052604081205461154d90600163ffffffff61198216565b6000838152600660205260409020549091508082146115e857600160a060020a038416600090815260056020526040812080548490811061158a57fe5b90600052602060002001549050806005600087600160a060020a0316600160a060020a0316815260200190815260200160002083815481106115c857fe5b600091825260208083209091019290925591825260069052604090208190555b600160a060020a0384166000908152600560205260409020805490611611906000198301611c0f565b5050505050565b600160a060020a0390911660009081526005602081815260408084208054868652600684529185208290559282526001810183559183529091200155565b600160a060020a0382166116b4576040805160e560020a62461bcd02815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f2061646472657373604482015290519081900360640190fd5b6116bd81610fd8565b15611712576040805160e560020a62461bcd02815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000604482015290519081900360640190fd5b6000818152600160209081526040808320805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03871690811790915583526003909152902061175e90611979565b6040518190600160a060020a038416906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b600780546000838152600860205260408120829055600182018355919091527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6880155565b6117e882826119e2565b6000818152600b6020526040902054600260001961010060018416150201909116041561113e576000818152600b6020526040812061113e91611c33565b61183082826111e8565b15611885576040805160e560020a62461bcd02815260206004820152601f60248201527f526f6c65733a206163636f756e7420616c72656164792068617320726f6c6500604482015290519081900360640190fd5b600160a060020a0316600090815260209190915260409020805460ff19166001179055565b6118b482826111e8565b6118f25760405160e560020a62461bcd028152600401808060200182810382526021815260200180611df66021913960400191505060405180910390fd5b600160a060020a0316600090815260209190915260409020805460ff19169055565b3b151590565b600081815260026020526040902054600160a060020a031615610b2e576000908152600260205260409020805473ffffffffffffffffffffffffffffffffffffffff19169055565b805461197590600163ffffffff61198216565b9055565b80546001019055565b6000828211156119dc576040805160e560020a62461bcd02815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b6119ec8282611a0e565b6119f68282611523565b60008181526006602052604081205561113e81611af5565b81600160a060020a0316611a2182610c01565b600160a060020a031614611a695760405160e560020a62461bcd028152600401808060200182810382526025815260200180611f676025913960400191505060405180910390fd5b611a728161191a565b600160a060020a0382166000908152600360205260409020611a9390611962565b600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916905551829190600160a060020a038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b600754600090611b0c90600163ffffffff61198216565b60008381526008602052604081205460078054939450909284908110611b2e57fe5b906000526020600020015490508060078381548110611b4957fe5b60009182526020808320909101929092558281526008909152604090208290556007805490611b7c906000198301611c0f565b50505060009182525060086020526040812055565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611bd257805160ff1916838001178555611bff565b82800160010185558215611bff579182015b82811115611bff578251825591602001919060010190611be4565b50611c0b929150611c73565b5090565b8154818355818111156109e8576000838152602090206109e8918101908301611c73565b50805460018160011615610100020316600290046000825580601f10611c595750610b2e565b601f016020900490600052602060002090810190610b2e91905b6107c991905b80821115611c0b5760008155600101611c7956fe455243373231456e756d657261626c653a206f776e657220696e646578206f7574206f6620626f756e64734552433732313a207472616e7366657220746f206e6f6e20455243373231526563656976657220696d706c656d656e7465724552433732313a207472616e7366657220746f20746865207a65726f20616464726573734552433732313a206f70657261746f7220717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a20617070726f76652063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f76656420666f7220616c6c4552433732313a2062616c616e636520717565727920666f7220746865207a65726f20616464726573734552433732313a206f776e657220717565727920666f72206e6f6e6578697374656e7420746f6b656e4d696e746572526f6c653a2063616c6c657220646f6573206e6f74206861766520746865204d696e74657220726f6c65526f6c65733a206163636f756e7420646f6573206e6f74206861766520726f6c654552433732313a20617070726f76656420717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732314d657461646174613a2055524920736574206f66206e6f6e6578697374656e7420746f6b656e526f6c65733a206163636f756e7420697320746865207a65726f20616464726573734552433732313a207472616e73666572206f6620746f6b656e2074686174206973206e6f74206f776e4552433732314d657461646174613a2055524920717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a20617070726f76616c20746f2063757272656e74206f776e65724552433732313a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564455243373231456e756d657261626c653a20676c6f62616c20696e646578206f7574206f6620626f756e64734552433732313a206275726e206f6620746f6b656e2074686174206973206e6f74206f776e4552433732314275726e61626c653a2063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564a265627a7a7231582024f3bd283d7604a1e2e6e190364fd0d97fa60330667855a543ae95fcdb5ef16d64736f6c634300050b0032526f6c65733a206163636f756e7420697320746865207a65726f2061646472657373", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061016a576000357c0100000000000000000000000000000000000000000000000000000000900480634f6ccce7116100e057806398650275116100995780639865027514610594578063a22cb4651461059c578063aa271e1a146105ca578063b88d4fde146105f0578063c87b56dd146106b6578063e985e9c5146106d35761016a565b80634f6ccce71461044b57806350bb4e7f146104685780636352211e1461052357806370a082311461054057806395d89b4114610566578063983b2d561461056e5761016a565b806318160ddd1161013257806318160ddd1461035057806323b872dd1461036a5780632f745c59146103a057806340c10f19146103cc57806342842e0e146103f857806342966c681461042e5761016a565b806301ffc9a71461016f57806306fdde03146101bf578063081812fc1461023c578063095ea7b314610275578063162094c4146102a3575b600080fd5b6101ab6004803603602081101561018557600080fd5b50357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916610701565b604080519115158252519081900360200190f35b6101c7610735565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102015781810151838201526020016101e9565b50505050905090810190601f16801561022e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102596004803603602081101561025257600080fd5b50356107cc565b60408051600160a060020a039092168252519081900360200190f35b6102a16004803603604081101561028b57600080fd5b50600160a060020a038135169060200135610831565b005b6101ab600480360360408110156102b957600080fd5b813591908101906040810160208201356401000000008111156102db57600080fd5b8201836020820111156102ed57600080fd5b8035906020019184600183028401116401000000008311171561030f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610955945050505050565b61035861098f565b60408051918252519081900360200190f35b6102a16004803603606081101561038057600080fd5b50600160a060020a03813581169160208101359091169060400135610995565b610358600480360360408110156103b657600080fd5b50600160a060020a0381351690602001356109ed565b6101ab600480360360408110156103e257600080fd5b50600160a060020a038135169060200135610a6f565b6102a16004803603606081101561040e57600080fd5b50600160a060020a03813581169160208101359091169060400135610ac2565b6102a16004803603602081101561044457600080fd5b5035610add565b6103586004803603602081101561046157600080fd5b5035610b31565b6101ab6004803603606081101561047e57600080fd5b600160a060020a03823516916020810135918101906060810160408201356401000000008111156104ae57600080fd5b8201836020820111156104c057600080fd5b803590602001918460018302840111640100000000831117156104e257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610b9a945050505050565b6102596004803603602081101561053957600080fd5b5035610c01565b6103586004803603602081101561055657600080fd5b5035600160a060020a0316610c5e565b6101c7610cc9565b6102a16004803603602081101561058457600080fd5b5035600160a060020a0316610d2a565b6102a1610d7a565b6102a1600480360360408110156105b257600080fd5b50600160a060020a0381351690602001351515610d85565b6101ab600480360360208110156105e057600080fd5b5035600160a060020a0316610e54565b6102a16004803603608081101561060657600080fd5b600160a060020a0382358116926020810135909116916040820135919081019060808101606082013564010000000081111561064157600080fd5b82018360208201111561065357600080fd5b8035906020019184600183028401116401000000008311171561067557600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610e67945050505050565b6101c7600480360360208110156106cc57600080fd5b5035610ec2565b6101ab600480360360408110156106e957600080fd5b50600160a060020a0381358116916020013516610faa565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191660009081526020819052604090205460ff1690565b60098054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156107c15780601f10610796576101008083540402835291602001916107c1565b820191906000526020600020905b8154815290600101906020018083116107a457829003601f168201915b505050505090505b90565b60006107d782610fd8565b6108155760405160e560020a62461bcd02815260040180806020018281038252602c815260200180611e17602c913960400191505060405180910390fd5b50600090815260026020526040902054600160a060020a031690565b600061083c82610c01565b905080600160a060020a031683600160a060020a031614156108925760405160e560020a62461bcd028152600401808060200182810382526021815260200180611ee96021913960400191505060405180910390fd5b33600160a060020a03821614806108ae57506108ae8133610faa565b6108ec5760405160e560020a62461bcd028152600401808060200182810382526038815260200180611d3b6038913960400191505060405180910390fd5b600082815260026020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0387811691821790925591518593918516917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a4505050565b600061096083610fd8565b61096957600080fd5b6109733384610ff5565b61097c57600080fd5b610986838361109c565b50600192915050565b60075490565b61099f3382610ff5565b6109dd5760405160e560020a62461bcd028152600401808060200182810382526031815260200180611f0a6031913960400191505060405180910390fd5b6109e8838383611102565b505050565b60006109f883610c5e565b8210610a385760405160e560020a62461bcd02815260040180806020018281038252602b815260200180611c8e602b913960400191505060405180910390fd5b600160a060020a0383166000908152600560205260409020805483908110610a5c57fe5b9060005260206000200154905092915050565b6000610a7a33610e54565b610ab85760405160e560020a62461bcd028152600401808060200182810382526030815260200180611dc66030913960400191505060405180910390fd5b6109868383611121565b6109e883838360405180602001604052806000815250610e67565b610ae73382610ff5565b610b255760405160e560020a62461bcd028152600401808060200182810382526030815260200180611f8c6030913960400191505060405180910390fd5b610b2e81611142565b50565b6000610b3b61098f565b8210610b7b5760405160e560020a62461bcd02815260040180806020018281038252602c815260200180611f3b602c913960400191505060405180910390fd5b60078281548110610b8857fe5b90600052602060002001549050919050565b6000610ba533610e54565b610be35760405160e560020a62461bcd028152600401808060200182810382526030815260200180611dc66030913960400191505060405180910390fd5b610bed8484611121565b610bf7838361109c565b5060019392505050565b600081815260016020526040812054600160a060020a031680610c585760405160e560020a62461bcd028152600401808060200182810382526029815260200180611d9d6029913960400191505060405180910390fd5b92915050565b6000600160a060020a038216610ca85760405160e560020a62461bcd02815260040180806020018281038252602a815260200180611d73602a913960400191505060405180910390fd5b600160a060020a0382166000908152600360205260409020610c5890611154565b600a8054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156107c15780601f10610796576101008083540402835291602001916107c1565b610d3333610e54565b610d715760405160e560020a62461bcd028152600401808060200182810382526030815260200180611dc66030913960400191505060405180910390fd5b610b2e81611158565b610d83336111a0565b565b600160a060020a038216331415610de6576040805160e560020a62461bcd02815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c657200000000000000604482015290519081900360640190fd5b336000818152600460209081526040808320600160a060020a03871680855290835292819020805460ff1916861515908117909155815190815290519293927f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31929181900390910190a35050565b6000610c58600c8363ffffffff6111e816565b610e72848484610995565b610e7e84848484611252565b610ebc5760405160e560020a62461bcd028152600401808060200182810382526032815260200180611cb96032913960400191505060405180910390fd5b50505050565b6060610ecd82610fd8565b610f0b5760405160e560020a62461bcd02815260040180806020018281038252602f815260200180611eba602f913960400191505060405180910390fd5b6000828152600b602090815260409182902080548351601f600260001961010060018616150201909316929092049182018490048402810184019094528084529091830182828015610f9e5780601f10610f7357610100808354040283529160200191610f9e565b820191906000526020600020905b815481529060010190602001808311610f8157829003601f168201915b50505050509050919050565b600160a060020a03918216600090815260046020908152604080832093909416825291909152205460ff1690565b600090815260016020526040902054600160a060020a0316151590565b600061100082610fd8565b61103e5760405160e560020a62461bcd02815260040180806020018281038252602c815260200180611d0f602c913960400191505060405180910390fd5b600061104983610c01565b905080600160a060020a031684600160a060020a03161480611084575083600160a060020a0316611079846107cc565b600160a060020a0316145b8061109457506110948185610faa565b949350505050565b6110a582610fd8565b6110e35760405160e560020a62461bcd02815260040180806020018281038252602c815260200180611e43602c913960400191505060405180910390fd5b6000828152600b6020908152604090912082516109e892840190611b91565b61110d8383836113cc565b6111178382611523565b6109e88282611618565b61112b8282611656565b6111358282611618565b61113e8161179a565b5050565b610b2e61114e82610c01565b826117de565b5490565b611169600c8263ffffffff61182616565b604051600160a060020a038216907f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f690600090a250565b6111b1600c8263ffffffff6118aa16565b604051600160a060020a038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669290600090a250565b6000600160a060020a0382166112325760405160e560020a62461bcd028152600401808060200182810382526022815260200180611e6f6022913960400191505060405180910390fd5b50600160a060020a03166000908152602091909152604090205460ff1690565b600061126684600160a060020a0316611914565b61127257506001611094565b6040517f150b7a020000000000000000000000000000000000000000000000000000000081523360048201818152600160a060020a03888116602485015260448401879052608060648501908152865160848601528651600095928a169463150b7a029490938c938b938b939260a4019060208501908083838e5b838110156113055781810151838201526020016112ed565b50505050905090810190601f1680156113325780820380516001836020036101000a031916815260200191505b5095505050505050602060405180830381600087803b15801561135457600080fd5b505af1158015611368573d6000803e3d6000fd5b505050506040513d602081101561137e57600080fd5b50517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f150b7a020000000000000000000000000000000000000000000000000000000014915050949350505050565b82600160a060020a03166113df82610c01565b600160a060020a0316146114275760405160e560020a62461bcd028152600401808060200182810382526029815260200180611e916029913960400191505060405180910390fd5b600160a060020a03821661146f5760405160e560020a62461bcd028152600401808060200182810382526024815260200180611ceb6024913960400191505060405180910390fd5b6114788161191a565b600160a060020a038316600090815260036020526040902061149990611962565b600160a060020a03821660009081526003602052604090206114ba90611979565b600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0386811691821790925591518493918716917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b600160a060020a03821660009081526005602052604081205461154d90600163ffffffff61198216565b6000838152600660205260409020549091508082146115e857600160a060020a038416600090815260056020526040812080548490811061158a57fe5b90600052602060002001549050806005600087600160a060020a0316600160a060020a0316815260200190815260200160002083815481106115c857fe5b600091825260208083209091019290925591825260069052604090208190555b600160a060020a0384166000908152600560205260409020805490611611906000198301611c0f565b5050505050565b600160a060020a0390911660009081526005602081815260408084208054868652600684529185208290559282526001810183559183529091200155565b600160a060020a0382166116b4576040805160e560020a62461bcd02815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f2061646472657373604482015290519081900360640190fd5b6116bd81610fd8565b15611712576040805160e560020a62461bcd02815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000604482015290519081900360640190fd5b6000818152600160209081526040808320805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03871690811790915583526003909152902061175e90611979565b6040518190600160a060020a038416906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b600780546000838152600860205260408120829055600182018355919091527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6880155565b6117e882826119e2565b6000818152600b6020526040902054600260001961010060018416150201909116041561113e576000818152600b6020526040812061113e91611c33565b61183082826111e8565b15611885576040805160e560020a62461bcd02815260206004820152601f60248201527f526f6c65733a206163636f756e7420616c72656164792068617320726f6c6500604482015290519081900360640190fd5b600160a060020a0316600090815260209190915260409020805460ff19166001179055565b6118b482826111e8565b6118f25760405160e560020a62461bcd028152600401808060200182810382526021815260200180611df66021913960400191505060405180910390fd5b600160a060020a0316600090815260209190915260409020805460ff19169055565b3b151590565b600081815260026020526040902054600160a060020a031615610b2e576000908152600260205260409020805473ffffffffffffffffffffffffffffffffffffffff19169055565b805461197590600163ffffffff61198216565b9055565b80546001019055565b6000828211156119dc576040805160e560020a62461bcd02815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b6119ec8282611a0e565b6119f68282611523565b60008181526006602052604081205561113e81611af5565b81600160a060020a0316611a2182610c01565b600160a060020a031614611a695760405160e560020a62461bcd028152600401808060200182810382526025815260200180611f676025913960400191505060405180910390fd5b611a728161191a565b600160a060020a0382166000908152600360205260409020611a9390611962565b600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916905551829190600160a060020a038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b600754600090611b0c90600163ffffffff61198216565b60008381526008602052604081205460078054939450909284908110611b2e57fe5b906000526020600020015490508060078381548110611b4957fe5b60009182526020808320909101929092558281526008909152604090208290556007805490611b7c906000198301611c0f565b50505060009182525060086020526040812055565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611bd257805160ff1916838001178555611bff565b82800160010185558215611bff579182015b82811115611bff578251825591602001919060010190611be4565b50611c0b929150611c73565b5090565b8154818355818111156109e8576000838152602090206109e8918101908301611c73565b50805460018160011615610100020316600290046000825580601f10611c595750610b2e565b601f016020900490600052602060002090810190610b2e91905b6107c991905b80821115611c0b5760008155600101611c7956fe455243373231456e756d657261626c653a206f776e657220696e646578206f7574206f6620626f756e64734552433732313a207472616e7366657220746f206e6f6e20455243373231526563656976657220696d706c656d656e7465724552433732313a207472616e7366657220746f20746865207a65726f20616464726573734552433732313a206f70657261746f7220717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a20617070726f76652063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f76656420666f7220616c6c4552433732313a2062616c616e636520717565727920666f7220746865207a65726f20616464726573734552433732313a206f776e657220717565727920666f72206e6f6e6578697374656e7420746f6b656e4d696e746572526f6c653a2063616c6c657220646f6573206e6f74206861766520746865204d696e74657220726f6c65526f6c65733a206163636f756e7420646f6573206e6f74206861766520726f6c654552433732313a20617070726f76656420717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732314d657461646174613a2055524920736574206f66206e6f6e6578697374656e7420746f6b656e526f6c65733a206163636f756e7420697320746865207a65726f20616464726573734552433732313a207472616e73666572206f6620746f6b656e2074686174206973206e6f74206f776e4552433732314d657461646174613a2055524920717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a20617070726f76616c20746f2063757272656e74206f776e65724552433732313a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564455243373231456e756d657261626c653a20676c6f62616c20696e646578206f7574206f6620626f756e64734552433732313a206275726e206f6620746f6b656e2074686174206973206e6f74206f776e4552433732314275726e61626c653a2063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564a265627a7a7231582024f3bd283d7604a1e2e6e190364fd0d97fa60330667855a543ae95fcdb5ef16d64736f6c634300050b0032", + "sourceMap": "178:882:2:-;;;240:195;8:9:-1;5:2;;;30:1;27;20:12;5:2;240:195:2;;;;;;;;;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;240:195:2;;;;;;;;;;;;;19:11:-1;14:3;11:20;8:2;;;44:1;41;34:12;8:2;62:21;;;;123:4;114:14;;138:31;;;135:2;;;182:1;179;172:12;135:2;213:10;;261:11;244:29;;285:43;;;282:58;-1:-1;233:115;230:2;;;361:1;358;351:12;230:2;372:25;;-1:-1;240:195:2;;420:4:-1;411:14;;;;240:195:2;;;;;411:14:-1;240:195:2;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;240:195:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;19:11:-1;14:3;11:20;8:2;;;44:1;41;34:12;8:2;62:21;;;;123:4;114:14;;138:31;;;135:2;;;182:1;179;172:12;135:2;213:10;;261:11;244:29;;285:43;;;282:58;-1:-1;233:115;230:2;;;361:1;358;351:12;230:2;372:25;;-1:-1;240:195:2;;420:4:-1;411:14;;;;240:195:2;;;;;411:14:-1;240:195:2;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;240:195:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;240:195:2;;-1:-1:-1;339:4:2;;-1:-1:-1;345:6:2;;-1:-1:-1;339:4:2;345:6;718:40:81;737:20;718:18;;;;:40;:::i;:::-;2220::89;2239:20;2220:18;;;;:40;:::i;:::-;1316:51:90;1335:31;1316:18;;;;:51;:::i;:::-;825:12:92;;;;:5;;:12;;;;;:::i;:::-;-1:-1:-1;847:16:92;;;;:7;;:16;;;;;:::i;:::-;-1:-1:-1;951:49:92;970:29;951:18;;;;:49;:::i;:::-;753:254;;452:155:91;;275:22:79;286:10;275;;;:22;;;:::i;:::-;240:195:2;;178:882;;1442:190:81;1517:25;;;;;;1509:66;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1585:33;;:20;:33;;;;;;;;;;:40;;-1:-1:-1;;1585:40:81;1621:4;1585:40;;;1442:190::o;737:119:79:-;793:21;:8;806:7;793:12;;;;;;:21;:::i;:::-;829:20;;-1:-1:-1;;;;;829:20:79;;;;;;;;737:119;:::o;260:175:78:-;337:18;341:4;347:7;337:3;;;;:18;:::i;:::-;336:19;328:63;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;401:20:78;:11;:20;;;;;;;;;;;:27;;-1:-1:-1;;401:27:78;424:4;401:27;;;260:175::o;779:200::-;851:4;-1:-1:-1;;;;;875:21:78;;867:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;952:20:78;:11;:20;;;;;;;;;;;;;;;779:200::o;178:882:2:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;178:882:2;;;-1:-1:-1;178:882:2;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;", + "deployedSourceMap": "178:882:2:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;178:882:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;915:133:81;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;915:133:81;-1:-1:-1;;915:133:81;;:::i;:::-;;;;;;;;;;;;;;;;;;1112:83:92;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;1112:83:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4237:200:89;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;4237:200:89;;:::i;:::-;;;;-1:-1:-1;;;;;4237:200:89;;;;;;;;;;;;;;3541:411;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;3541:411:89;;;;;;;;:::i;:::-;;792:266:2;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;792:266:2;;;;;;;;;;;;;;21:11:-1;5:28;;2:2;;;46:1;43;36:12;2:2;792:266:2;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;792:266:2;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;792:266:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;792:266:2;;-1:-1:-1;792:266:2;;-1:-1:-1;;;;;792:266:2:i;2130:94:90:-;;;:::i;:::-;;;;;;;;;;;;;;;;5877:285:89;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;5877:285:89;;;;;;;;;;;;;;;;;:::i;1748:229:90:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;1748:229:90;;;;;;;;:::i;441:160:2:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;441:160:2;;;;;;;;:::i;6795:132:89:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;6795:132:89;;;;;;;;;;;;;;;;;:::i;607:179:2:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;607:179:2;;:::i;2562:196:90:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;2562:196:90;;:::i;557:209:93:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;557:209:93;;;;;;;;;;;;;;;;;;;21:11:-1;5:28;;2:2;;;46:1;43;36:12;2:2;557:209:93;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;557:209:93;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;557:209:93;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;557:209:93;;-1:-1:-1;557:209:93;;-1:-1:-1;;;;;557:209:93:i;2897:223:89:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;2897:223:89;;:::i;2471:207::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;2471:207:89;-1:-1:-1;;;;;2471:207:89;;:::i;1304:87:92:-;;;:::i;560:90:79:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;560:90:79;-1:-1:-1;;;;;560:90:79;;:::i;656:75::-;;;:::i;4730:243:89:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;4730:243:89;;;;;;;;;;:::i;447:107:79:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;447:107:79;-1:-1:-1;;;;;447:107:79;;:::i;7632:265:89:-;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;-1:-1;;;;;7632:265:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;5:28;;2:2;;;46:1;43;36:12;2:2;7632:265:89;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;7632:265:89;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;7632:265:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;7632:265:89;;-1:-1:-1;7632:265:89;;-1:-1:-1;;;;;7632:265:89:i;1591:202:92:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;1591:202:92;;:::i;5295:145:89:-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;5295:145:89;;;;;;;;;;:::i;915:133:81:-;-1:-1:-1;;1008:33:81;985:4;1008:33;;;;;;;;;;;;;;915:133::o;1112:83:92:-;1183:5;1176:12;;;;;;;;-1:-1:-1;;1176:12:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1151:13;;1176:12;;1183:5;;1176:12;;1183:5;1176:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1112:83;;:::o;4237:200:89:-;4296:7;4323:16;4331:7;4323;:16::i;:::-;4315:73;;;;-1:-1:-1;;;;;4315:73:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;4406:24:89;;;;:15;:24;;;;;;-1:-1:-1;;;;;4406:24:89;;4237:200::o;3541:411::-;3604:13;3620:16;3628:7;3620;:16::i;:::-;3604:32;;3660:5;-1:-1:-1;;;;;3654:11:89;:2;-1:-1:-1;;;;;3654:11:89;;;3646:57;;;;-1:-1:-1;;;;;3646:57:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3722:10;-1:-1:-1;;;;;3722:19:89;;;;:58;;;3745:35;3762:5;3769:10;3745:16;:35::i;:::-;3714:148;;;;-1:-1:-1;;;;;3714:148:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3873:24;;;;:15;:24;;;;;;:29;;-1:-1:-1;;3873:29:89;-1:-1:-1;;;;;3873:29:89;;;;;;;;;3917:28;;3873:24;;3917:28;;;;;;;3541:411;;;:::o;792:266:2:-;886:4;914:16;922:7;914;:16::i;:::-;906:25;;;;;;949:39;968:10;980:7;949:18;:39::i;:::-;941:48;;;;;;999:31;1012:7;1021:8;999:12;:31::i;:::-;-1:-1:-1;1047:4:2;792:266;;;;:::o;2130:94:90:-;2200:10;:17;2130:94;:::o;5877:285:89:-;6019:39;6038:10;6050:7;6019:18;:39::i;:::-;6011:101;;;;-1:-1:-1;;;;;6011:101:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6123:32;6137:4;6143:2;6147:7;6123:13;:32::i;:::-;5877:285;;;:::o;1748:229:90:-;1828:7;1863:16;1873:5;1863:9;:16::i;:::-;1855:5;:24;1847:80;;;;-1:-1:-1;;;;;1847:80:90;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;1944:19:90;;;;;;:12;:19;;;;;:26;;1964:5;;1944:26;;;;;;;;;;;;;;1937:33;;1748:229;;;;:::o;441:160:2:-;535:4;350:20:79;359:10;350:8;:20::i;:::-;342:81;;;;-1:-1:-1;;;;;342:81:79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;555:18:2;561:2;565:7;555:5;:18::i;6795:132:89:-;6881:39;6898:4;6904:2;6908:7;6881:39;;;;;;;;;;;;:16;:39::i;607:179:2:-;663:39;682:10;694:7;663:18;:39::i;:::-;655:100;;;;-1:-1:-1;;;;;655:100:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;765:14;771:7;765:5;:14::i;:::-;607:179;:::o;2562:196:90:-;2620:7;2655:13;:11;:13::i;:::-;2647:5;:21;2639:78;;;;-1:-1:-1;;;;;2639:78:90;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2734:10;2745:5;2734:17;;;;;;;;;;;;;;;;2727:24;;2562:196;;;:::o;557:209:93:-;663:4;350:20:79;359:10;350:8;:20::i;:::-;342:81;;;;-1:-1:-1;;;;;342:81:79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;679:18:93;685:2;689:7;679:5;:18::i;:::-;707:31;720:7;729:8;707:12;:31::i;:::-;-1:-1:-1;755:4:93;557:209;;;;;:::o;2897:223:89:-;2952:7;2987:20;;;:11;:20;;;;;;-1:-1:-1;;;;;2987:20:89;3025:19;3017:73;;;;-1:-1:-1;;;;;3017:73:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3108:5;2897:223;-1:-1:-1;;2897:223:89:o;2471:207::-;2526:7;-1:-1:-1;;;;;2553:19:89;;2545:74;;;;-1:-1:-1;;;;;2545:74:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2637:24:89;;;;;;:17;:24;;;;;:34;;:32;:34::i;1304:87:92:-;1377:7;1370:14;;;;;;;;-1:-1:-1;;1370:14:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1345:13;;1370:14;;1377:7;;1370:14;;1377:7;1370:14;;;;;;;;;;;;;;;;;;;;;;;;560:90:79;350:20;359:10;350:8;:20::i;:::-;342:81;;;;-1:-1:-1;;;;;342:81:79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;624:19;635:7;624:10;:19::i;656:75::-;699:25;713:10;699:13;:25::i;:::-;656:75::o;4730:243:89:-;-1:-1:-1;;;;;4809:16:89;;4815:10;4809:16;;4801:54;;;;;-1:-1:-1;;;;;4801:54:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;4885:10;4866:30;;;;:18;:30;;;;;;;;-1:-1:-1;;;;;4866:34:89;;;;;;;;;;;;:45;;-1:-1:-1;;4866:45:89;;;;;;;;;;4926:40;;;;;;;4866:34;;4885:10;4926:40;;;;;;;;;;;4730:243;;:::o;447:107:79:-;503:4;526:21;:8;539:7;526:21;:12;:21;:::i;7632:265:89:-;7738:31;7751:4;7757:2;7761:7;7738:12;:31::i;:::-;7787:48;7810:4;7816:2;7820:7;7829:5;7787:22;:48::i;:::-;7779:111;;;;-1:-1:-1;;;;;7779:111:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7632:265;;;;:::o;1591:202:92:-;1649:13;1682:16;1690:7;1682;:16::i;:::-;1674:76;;;;-1:-1:-1;;;;;1674:76:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1767:19;;;;:10;:19;;;;;;;;;1760:26;;;;;;-1:-1:-1;;1760:26:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1767:19;;1760:26;;1767:19;1760:26;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1591:202;;;:::o;5295:145:89:-;-1:-1:-1;;;;;5398:25:89;;;5375:4;5398:25;;;:18;:25;;;;;;;;:35;;;;;;;;;;;;;;;5295:145::o;8092:152::-;8149:4;8181:20;;;:11;:20;;;;;;-1:-1:-1;;;;;8181:20:89;8218:19;;;8092:152::o;8605:329::-;8690:4;8714:16;8722:7;8714;:16::i;:::-;8706:73;;;;-1:-1:-1;;;;;8706:73:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8789:13;8805:16;8813:7;8805;:16::i;:::-;8789:32;;8850:5;-1:-1:-1;;;;;8839:16:89;:7;-1:-1:-1;;;;;8839:16:89;;:51;;;;8883:7;-1:-1:-1;;;;;8859:31:89;:20;8871:7;8859:11;:20::i;:::-;-1:-1:-1;;;;;8859:31:89;;8839:51;:87;;;;8894:32;8911:5;8918:7;8894:16;:32::i;:::-;8831:96;8605:329;-1:-1:-1;;;;8605:329:89:o;2032:192:92:-;2117:16;2125:7;2117;:16::i;:::-;2109:73;;;;-1:-1:-1;;;;;2109:73:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2192:19;;;;:10;:19;;;;;;;;:25;;;;;;;;:::i;3133:239:90:-;3218:38;3238:4;3244:2;3248:7;3218:19;:38::i;:::-;3267:47;3300:4;3306:7;3267:32;:47::i;:::-;3325:40;3353:2;3357:7;3325:27;:40::i;3629:196::-;3692:24;3704:2;3708:7;3692:11;:24::i;:::-;3727:40;3755:2;3759:7;3727:27;:40::i;:::-;3778;3810:7;3778:31;:40::i;:::-;3629:196;;:::o;10286:90:89:-;10337:32;10343:16;10351:7;10343;:16::i;:::-;10361:7;10337:5;:32::i;1063:112:80:-;1154:14;;1063:112::o;737:119:79:-;793:21;:8;806:7;793:21;:12;:21;:::i;:::-;829:20;;-1:-1:-1;;;;;829:20:79;;;;;;;;737:119;:::o;862:127::-;921:24;:8;937:7;921:24;:15;:24;:::i;:::-;960:22;;-1:-1:-1;;;;;960:22:79;;;;;;;;862:127;:::o;779:200:78:-;851:4;-1:-1:-1;;;;;875:21:78;;867:68;;;;-1:-1:-1;;;;;867:68:78;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;952:20:78;:11;:20;;;;;;;;;;;;;;;779:200::o;11771:347:89:-;11892:4;11917:15;:2;-1:-1:-1;;;;;11917:13:89;;:15::i;:::-;11912:58;;-1:-1:-1;11955:4:89;11948:11;;11912:58;11996:70;;;;;12033:10;11996:70;;;;;;-1:-1:-1;;;;;11996:70:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;11980:13;;11996:36;;;;;;12033:10;;12045:4;;12051:7;;12060:5;;11996:70;;;;;;;;;;;11980:13;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;11996:70:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;11996:70:89;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;11996:70:89;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;11996:70:89;-1:-1:-1;;12084:26:89;12094:16;12084:26;;-1:-1:-1;;11771:347:89;;;;;;:::o;10751:447::-;10864:4;-1:-1:-1;;;;;10844:24:89;:16;10852:7;10844;:16::i;:::-;-1:-1:-1;;;;;10844:24:89;;10836:78;;;;-1:-1:-1;;;;;10836:78:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;10932:16:89;;10924:65;;;;-1:-1:-1;;;;;10924:65:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11000:23;11015:7;11000:14;:23::i;:::-;-1:-1:-1;;;;;11034:23:89;;;;;;:17;:23;;;;;:35;;:33;:35::i;:::-;-1:-1:-1;;;;;11079:21:89;;;;;;:17;:21;;;;;:33;;:31;:33::i;:::-;11123:20;;;;:11;:20;;;;;;:25;;-1:-1:-1;;11123:25:89;-1:-1:-1;;;;;11123:25:89;;;;;;;;;11164:27;;11123:20;;11164:27;;;;;;;10751:447;;;:::o;6241:1128:90:-;-1:-1:-1;;;;;6528:18:90;;6503:22;6528:18;;;:12;:18;;;;;:25;:32;;6558:1;6528:32;:29;:32;:::i;:::-;6570:18;6591:26;;;:17;:26;;;;;;6503:57;;-1:-1:-1;6721:28:90;;;6717:323;;-1:-1:-1;;;;;6787:18:90;;6765:19;6787:18;;;:12;:18;;;;;:34;;6806:14;;6787:34;;;;;;;;;;;;;;6765:56;;6869:11;6836:12;:18;6849:4;-1:-1:-1;;;;;6836:18:90;-1:-1:-1;;;;;6836:18:90;;;;;;;;;;;;6855:10;6836:30;;;;;;;;;;;;;;;;;;;:44;;;;6952:30;;;:17;:30;;;;;:43;;;6717:323;-1:-1:-1;;;;;7126:18:90;;;;;;:12;:18;;;;;:27;;;;;-1:-1:-1;;7126:27:90;;;:::i;:::-;;6241:1128;;;;:::o;5087:183::-;-1:-1:-1;;;;;5200:16:90;;;;;;;:12;:16;;;;;;;;:23;;5171:26;;;:17;:26;;;;;:52;;;5233:16;;;39:1:-1;23:18;;45:23;;5233:30:90;;;;;;;;5087:183::o;9179:327:89:-;-1:-1:-1;;;;;9250:16:89;;9242:61;;;;;-1:-1:-1;;;;;9242:61:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9322:16;9330:7;9322;:16::i;:::-;9321:17;9313:58;;;;;-1:-1:-1;;;;;9313:58:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;9382:20;;;;:11;:20;;;;;;;;:25;;-1:-1:-1;;9382:25:89;-1:-1:-1;;;;;9382:25:89;;;;;;;;9417:21;;:17;:21;;;;;:33;;:31;:33::i;:::-;9466;;9491:7;;-1:-1:-1;;;;;9466:33:89;;;9483:1;;9466:33;;9483:1;;9466:33;9179:327;;:::o;5465:161:90:-;5568:10;:17;;5541:24;;;;:15;:24;;;;;:44;;;39:1:-1;23:18;;45:23;;5595:24:90;;;;;;;5465:161::o;2517:240:92:-;2583:27;2595:5;2602:7;2583:11;:27::i;:::-;2666:19;;;;:10;:19;;;;;2660:33;;-1:-1:-1;;2660:33:92;;;;;;;;;;;:38;2656:95;;2721:19;;;;:10;:19;;;;;2714:26;;;:::i;260:175:78:-;337:18;341:4;347:7;337:3;:18::i;:::-;336:19;328:63;;;;;-1:-1:-1;;;;;328:63:78;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;401:20:78;:11;:20;;;;;;;;;;;:27;;-1:-1:-1;;401:27:78;424:4;401:27;;;260:175::o;510:180::-;589:18;593:4;599:7;589:3;:18::i;:::-;581:64;;;;-1:-1:-1;;;;;581:64:78;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;655:20:78;678:5;655:20;;;;;;;;;;;:28;;-1:-1:-1;;655:28:78;;;510:180::o;542:413:98:-;902:20;940:8;;;542:413::o;12280:171:89:-;12379:1;12343:24;;;:15;:24;;;;;;-1:-1:-1;;;;;12343:24:89;:38;12339:106;;12432:1;12397:24;;;:15;:24;;;;;:37;;-1:-1:-1;;12397:37:89;;;12280:171::o;1276:108:80:-;1356:14;;:21;;1375:1;1356:21;:18;:21;:::i;:::-;1339:38;;1276:108::o;1181:89::-;1244:19;;1262:1;1244:19;;;1181:89::o;1274:179:83:-;1332:7;1364:1;1359;:6;;1351:49;;;;;-1:-1:-1;;;;;1351:49:83;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1422:5:83;;;1274:179::o;4100:364:90:-;4166:27;4178:5;4185:7;4166:11;:27::i;:::-;4204:48;4237:5;4244:7;4204:32;:48::i;:::-;4400:1;4371:26;;;:17;:26;;;;;:30;4412:45;4389:7;4412:36;:45::i;9781:324:89:-;9875:5;-1:-1:-1;;;;;9855:25:89;:16;9863:7;9855;:16::i;:::-;-1:-1:-1;;;;;9855:25:89;;9847:75;;;;-1:-1:-1;;;;;9847:75:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9933:23;9948:7;9933:14;:23::i;:::-;-1:-1:-1;;;;;9967:24:89;;;;;;:17;:24;;;;;:36;;:34;:36::i;:::-;10044:1;10013:20;;;:11;:20;;;;;;:33;;-1:-1:-1;;10013:33:89;;;10062:36;10025:7;;10044:1;-1:-1:-1;;;;;10062:36:89;;;;;10044:1;;10062:36;9781:324;;:::o;7657:1064:90:-;7931:10;:17;7906:22;;7931:24;;7953:1;7931:24;:21;:24;:::i;:::-;7965:18;7986:24;;;:15;:24;;;;;;8354:10;:26;;7906:49;;-1:-1:-1;7986:24:90;;7906:49;;8354:26;;;;;;;;;;;;;;8332:48;;8416:11;8391:10;8402;8391:22;;;;;;;;;;;;;;;;;;;:36;;;;8495:28;;;:15;:28;;;;;;:41;;;8657:10;:19;;;;;-1:-1:-1;;8657:19:90;;;:::i;:::-;-1:-1:-1;;;8713:1:90;8686:24;;;-1:-1:-1;8686:15:90;:24;;;;;:28;7657:1064::o;178:882:2:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;178:882:2;;;-1:-1:-1;178:882:2;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;", + "source": "pragma solidity ^0.5.0;\n\nimport 'openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol';\nimport 'openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol';\n\ncontract VASYA721 is ERC721Full, ERC721MetadataMintable {\n constructor(\n string memory name,\n string memory symbol\n )\n ERC721Full(name, symbol)\n public\n {\n // solium-disable-previous-line no-empty-blocks\n }\n\n function mint(address to, uint256 tokenId)\n public\n onlyMinter\n returns (bool)\n {\n _mint(to, tokenId);\n return true;\n }\n\n function burn(uint256 tokenId) public {\n require(_isApprovedOrOwner(msg.sender, tokenId), \"ERC721Burnable: caller is not owner nor approved\");\n _burn(tokenId);\n }\n\n function setTokenURI(uint256 tokenId, string memory tokenURI)\n public\n returns (bool)\n {\n require(_exists(tokenId));\n require(_isApprovedOrOwner(msg.sender, tokenId));\n _setTokenURI(tokenId, tokenURI);\n return true;\n }\n}", + "sourcePath": "/home/elvis/work/d2/erc721/contracts/VASYA721.sol", + "ast": { + "absolutePath": "/home/elvis/work/d2/erc721/contracts/VASYA721.sol", + "exportedSymbols": { + "VASYA721": [ + 214 + ] + }, + "id": 215, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 126, + "literals": [ + "solidity", + "^", + "0.5", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "0:23:2" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol", + "file": "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol", + "id": 127, + "nodeType": "ImportDirective", + "scope": 215, + "sourceUnit": 10887, + "src": "25:69:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol", + "file": "openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol", + "id": 128, + "nodeType": "ImportDirective", + "scope": 215, + "sourceUnit": 11053, + "src": "95:81:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 129, + "name": "ERC721Full", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 10886, + "src": "199:10:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC721Full_$10886", + "typeString": "contract ERC721Full" + } + }, + "id": 130, + "nodeType": "InheritanceSpecifier", + "src": "199:10:2" + }, + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 131, + "name": "ERC721MetadataMintable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 11052, + "src": "211:22:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC721MetadataMintable_$11052", + "typeString": "contract ERC721MetadataMintable" + } + }, + "id": 132, + "nodeType": "InheritanceSpecifier", + "src": "211:22:2" + } + ], + "contractDependencies": [ + 9105, + 9206, + 9216, + 10525, + 10862, + 10886, + 11015, + 11052, + 11155, + 11182, + 11205 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 214, + "linearizedBaseContracts": [ + 214, + 11052, + 9105, + 10886, + 11015, + 11205, + 10862, + 11182, + 10525, + 11155, + 9206, + 9216 + ], + "name": "VASYA721", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": { + "id": 143, + "nodeType": "Block", + "src": "372:63:2", + "statements": [] + }, + "documentation": null, + "id": 144, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "id": 139, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 134, + "src": "339:4:2", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + { + "argumentTypes": null, + "id": 140, + "name": "symbol", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 136, + "src": "345:6:2", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "id": 141, + "modifierName": { + "argumentTypes": null, + "id": 138, + "name": "ERC721Full", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10886, + "src": "328:10:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ERC721Full_$10886_$", + "typeString": "type(contract ERC721Full)" + } + }, + "nodeType": "ModifierInvocation", + "src": "328:24:2" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 137, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 134, + "name": "name", + "nodeType": "VariableDeclaration", + "scope": 144, + "src": "261:18:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 133, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "261:6:2", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 136, + "name": "symbol", + "nodeType": "VariableDeclaration", + "scope": 144, + "src": "289:20:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 135, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "289:6:2", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "251:68:2" + }, + "returnParameters": { + "id": 142, + "nodeType": "ParameterList", + "parameters": [], + "src": "372:0:2" + }, + "scope": 214, + "src": "240:195:2", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 162, + "nodeType": "Block", + "src": "545:56:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 156, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 146, + "src": "561:2:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 157, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 148, + "src": "565:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 155, + "name": "_mint", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 10669 + ], + "referencedDeclaration": 10669, + "src": "555:5:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 158, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "555:18:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 159, + "nodeType": "ExpressionStatement", + "src": "555:18:2" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 160, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "590:4:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 154, + "id": 161, + "nodeType": "Return", + "src": "583:11:2" + } + ] + }, + "documentation": null, + "id": 163, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 151, + "modifierName": { + "argumentTypes": null, + "id": 150, + "name": "onlyMinter", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9038, + "src": "507:10:2", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "507:10:2" + } + ], + "name": "mint", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 149, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 146, + "name": "to", + "nodeType": "VariableDeclaration", + "scope": 163, + "src": "455:10:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 145, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "455:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 148, + "name": "tokenId", + "nodeType": "VariableDeclaration", + "scope": 163, + "src": "467:15:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 147, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "467:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "454:29:2" + }, + "returnParameters": { + "id": 154, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 153, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 163, + "src": "535:4:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 152, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "535:4:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "534:6:2" + }, + "scope": 214, + "src": "441:160:2", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 181, + "nodeType": "Block", + "src": "645:141:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 170, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 11255, + "src": "682:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 171, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "682:10:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 172, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 165, + "src": "694:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 169, + "name": "_isApprovedOrOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10304, + "src": "663:18:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) view returns (bool)" + } + }, + "id": 173, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "663:39:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "4552433732314275726e61626c653a2063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564", + "id": 174, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "704:50:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_ee6b7e810d7b317242d4688e6943ff4dd7897bb01d903b1a666812481b12a4f1", + "typeString": "literal_string \"ERC721Burnable: caller is not owner nor approved\"" + }, + "value": "ERC721Burnable: caller is not owner nor approved" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_ee6b7e810d7b317242d4688e6943ff4dd7897bb01d903b1a666812481b12a4f1", + "typeString": "literal_string \"ERC721Burnable: caller is not owner nor approved\"" + } + ], + "id": 168, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 11258, + 11259 + ], + "referencedDeclaration": 11259, + "src": "655:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 175, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "655:100:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 176, + "nodeType": "ExpressionStatement", + "src": "655:100:2" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 178, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 165, + "src": "771:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 177, + "name": "_burn", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 11014, + 10405 + ], + "referencedDeclaration": 10405, + "src": "765:5:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 179, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "765:14:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 180, + "nodeType": "ExpressionStatement", + "src": "765:14:2" + } + ] + }, + "documentation": null, + "id": 182, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "burn", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 166, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 165, + "name": "tokenId", + "nodeType": "VariableDeclaration", + "scope": 182, + "src": "621:15:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 164, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "621:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "620:17:2" + }, + "returnParameters": { + "id": 167, + "nodeType": "ParameterList", + "parameters": [], + "src": "645:0:2" + }, + "scope": 214, + "src": "607:179:2", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 212, + "nodeType": "Block", + "src": "896:162:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 193, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 184, + "src": "922:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 192, + "name": "_exists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10265, + "src": "914:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_uint256_$returns$_t_bool_$", + "typeString": "function (uint256) view returns (bool)" + } + }, + "id": 194, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "914:16:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 191, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 11258, + 11259 + ], + "referencedDeclaration": 11258, + "src": "906:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 195, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "906:25:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 196, + "nodeType": "ExpressionStatement", + "src": "906:25:2" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 199, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 11255, + "src": "968:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 200, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "968:10:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 201, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 184, + "src": "980:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 198, + "name": "_isApprovedOrOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10304, + "src": "949:18:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) view returns (bool)" + } + }, + "id": 202, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "949:39:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 197, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 11258, + 11259 + ], + "referencedDeclaration": 11258, + "src": "941:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 203, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "941:48:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 204, + "nodeType": "ExpressionStatement", + "src": "941:48:2" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 206, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 184, + "src": "1012:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 207, + "name": "tokenURI", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 186, + "src": "1021:8:2", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + ], + "id": 205, + "name": "_setTokenURI", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10984, + "src": "999:12:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (uint256,string memory)" + } + }, + "id": 208, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "999:31:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 209, + "nodeType": "ExpressionStatement", + "src": "999:31:2" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 210, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1047:4:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 190, + "id": 211, + "nodeType": "Return", + "src": "1040:11:2" + } + ] + }, + "documentation": null, + "id": 213, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setTokenURI", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 187, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 184, + "name": "tokenId", + "nodeType": "VariableDeclaration", + "scope": 213, + "src": "813:15:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 183, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "813:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 186, + "name": "tokenURI", + "nodeType": "VariableDeclaration", + "scope": 213, + "src": "830:22:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 185, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "830:6:2", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "812:41:2" + }, + "returnParameters": { + "id": 190, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 189, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 213, + "src": "886:4:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 188, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "886:4:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "885:6:2" + }, + "scope": 214, + "src": "792:266:2", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + } + ], + "scope": 215, + "src": "178:882:2" + } + ], + "src": "0:1060:2" + }, + "legacyAST": { + "absolutePath": "/home/elvis/work/d2/erc721/contracts/VASYA721.sol", + "exportedSymbols": { + "VASYA721": [ + 214 + ] + }, + "id": 215, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 126, + "literals": [ + "solidity", + "^", + "0.5", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "0:23:2" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol", + "file": "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol", + "id": 127, + "nodeType": "ImportDirective", + "scope": 215, + "sourceUnit": 10887, + "src": "25:69:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol", + "file": "openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol", + "id": 128, + "nodeType": "ImportDirective", + "scope": 215, + "sourceUnit": 11053, + "src": "95:81:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 129, + "name": "ERC721Full", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 10886, + "src": "199:10:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC721Full_$10886", + "typeString": "contract ERC721Full" + } + }, + "id": 130, + "nodeType": "InheritanceSpecifier", + "src": "199:10:2" + }, + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 131, + "name": "ERC721MetadataMintable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 11052, + "src": "211:22:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC721MetadataMintable_$11052", + "typeString": "contract ERC721MetadataMintable" + } + }, + "id": 132, + "nodeType": "InheritanceSpecifier", + "src": "211:22:2" + } + ], + "contractDependencies": [ + 9105, + 9206, + 9216, + 10525, + 10862, + 10886, + 11015, + 11052, + 11155, + 11182, + 11205 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 214, + "linearizedBaseContracts": [ + 214, + 11052, + 9105, + 10886, + 11015, + 11205, + 10862, + 11182, + 10525, + 11155, + 9206, + 9216 + ], + "name": "VASYA721", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": { + "id": 143, + "nodeType": "Block", + "src": "372:63:2", + "statements": [] + }, + "documentation": null, + "id": 144, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "id": 139, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 134, + "src": "339:4:2", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + { + "argumentTypes": null, + "id": 140, + "name": "symbol", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 136, + "src": "345:6:2", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "id": 141, + "modifierName": { + "argumentTypes": null, + "id": 138, + "name": "ERC721Full", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10886, + "src": "328:10:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ERC721Full_$10886_$", + "typeString": "type(contract ERC721Full)" + } + }, + "nodeType": "ModifierInvocation", + "src": "328:24:2" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 137, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 134, + "name": "name", + "nodeType": "VariableDeclaration", + "scope": 144, + "src": "261:18:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 133, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "261:6:2", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 136, + "name": "symbol", + "nodeType": "VariableDeclaration", + "scope": 144, + "src": "289:20:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 135, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "289:6:2", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "251:68:2" + }, + "returnParameters": { + "id": 142, + "nodeType": "ParameterList", + "parameters": [], + "src": "372:0:2" + }, + "scope": 214, + "src": "240:195:2", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 162, + "nodeType": "Block", + "src": "545:56:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 156, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 146, + "src": "561:2:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 157, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 148, + "src": "565:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 155, + "name": "_mint", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 10669 + ], + "referencedDeclaration": 10669, + "src": "555:5:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 158, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "555:18:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 159, + "nodeType": "ExpressionStatement", + "src": "555:18:2" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 160, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "590:4:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 154, + "id": 161, + "nodeType": "Return", + "src": "583:11:2" + } + ] + }, + "documentation": null, + "id": 163, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 151, + "modifierName": { + "argumentTypes": null, + "id": 150, + "name": "onlyMinter", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9038, + "src": "507:10:2", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "507:10:2" + } + ], + "name": "mint", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 149, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 146, + "name": "to", + "nodeType": "VariableDeclaration", + "scope": 163, + "src": "455:10:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 145, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "455:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 148, + "name": "tokenId", + "nodeType": "VariableDeclaration", + "scope": 163, + "src": "467:15:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 147, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "467:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "454:29:2" + }, + "returnParameters": { + "id": 154, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 153, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 163, + "src": "535:4:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 152, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "535:4:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "534:6:2" + }, + "scope": 214, + "src": "441:160:2", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 181, + "nodeType": "Block", + "src": "645:141:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 170, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 11255, + "src": "682:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 171, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "682:10:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 172, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 165, + "src": "694:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 169, + "name": "_isApprovedOrOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10304, + "src": "663:18:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) view returns (bool)" + } + }, + "id": 173, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "663:39:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "4552433732314275726e61626c653a2063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564", + "id": 174, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "704:50:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_ee6b7e810d7b317242d4688e6943ff4dd7897bb01d903b1a666812481b12a4f1", + "typeString": "literal_string \"ERC721Burnable: caller is not owner nor approved\"" + }, + "value": "ERC721Burnable: caller is not owner nor approved" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_ee6b7e810d7b317242d4688e6943ff4dd7897bb01d903b1a666812481b12a4f1", + "typeString": "literal_string \"ERC721Burnable: caller is not owner nor approved\"" + } + ], + "id": 168, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 11258, + 11259 + ], + "referencedDeclaration": 11259, + "src": "655:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 175, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "655:100:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 176, + "nodeType": "ExpressionStatement", + "src": "655:100:2" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 178, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 165, + "src": "771:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 177, + "name": "_burn", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 11014, + 10405 + ], + "referencedDeclaration": 10405, + "src": "765:5:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 179, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "765:14:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 180, + "nodeType": "ExpressionStatement", + "src": "765:14:2" + } + ] + }, + "documentation": null, + "id": 182, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "burn", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 166, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 165, + "name": "tokenId", + "nodeType": "VariableDeclaration", + "scope": 182, + "src": "621:15:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 164, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "621:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "620:17:2" + }, + "returnParameters": { + "id": 167, + "nodeType": "ParameterList", + "parameters": [], + "src": "645:0:2" + }, + "scope": 214, + "src": "607:179:2", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 212, + "nodeType": "Block", + "src": "896:162:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 193, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 184, + "src": "922:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 192, + "name": "_exists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10265, + "src": "914:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_uint256_$returns$_t_bool_$", + "typeString": "function (uint256) view returns (bool)" + } + }, + "id": 194, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "914:16:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 191, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 11258, + 11259 + ], + "referencedDeclaration": 11258, + "src": "906:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 195, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "906:25:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 196, + "nodeType": "ExpressionStatement", + "src": "906:25:2" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 199, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 11255, + "src": "968:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 200, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "968:10:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 201, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 184, + "src": "980:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 198, + "name": "_isApprovedOrOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10304, + "src": "949:18:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) view returns (bool)" + } + }, + "id": 202, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "949:39:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 197, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 11258, + 11259 + ], + "referencedDeclaration": 11258, + "src": "941:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 203, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "941:48:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 204, + "nodeType": "ExpressionStatement", + "src": "941:48:2" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 206, + "name": "tokenId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 184, + "src": "1012:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 207, + "name": "tokenURI", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 186, + "src": "1021:8:2", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + ], + "id": 205, + "name": "_setTokenURI", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 10984, + "src": "999:12:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (uint256,string memory)" + } + }, + "id": 208, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "999:31:2", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 209, + "nodeType": "ExpressionStatement", + "src": "999:31:2" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 210, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1047:4:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 190, + "id": 211, + "nodeType": "Return", + "src": "1040:11:2" + } + ] + }, + "documentation": null, + "id": 213, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setTokenURI", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 187, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 184, + "name": "tokenId", + "nodeType": "VariableDeclaration", + "scope": 213, + "src": "813:15:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 183, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "813:7:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 186, + "name": "tokenURI", + "nodeType": "VariableDeclaration", + "scope": 213, + "src": "830:22:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 185, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "830:6:2", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "812:41:2" + }, + "returnParameters": { + "id": 190, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 189, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 213, + "src": "886:4:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 188, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "886:4:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "885:6:2" + }, + "scope": 214, + "src": "792:266:2", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + } + ], + "scope": 215, + "src": "178:882:2" + } + ], + "src": "0:1060:2" + }, + "compiler": { + "name": "solc", + "version": "0.5.11+commit.c082d0b4.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.0.11", + "updatedAt": "2019-09-06T10:41:38.933Z", + "devdoc": { + "methods": { + "approve(address,uint256)": { + "details": "Approves another address to transfer the given token ID The zero address indicates there is no approved address. There can only be one approved address per token at a given time. Can only be called by the token owner or an approved operator.", + "params": { + "to": "address to be approved for the given token ID", + "tokenId": "uint256 ID of the token to be approved" + } + }, + "balanceOf(address)": { + "details": "Gets the balance of the specified address.", + "params": { + "owner": "address to query the balance of" + }, + "return": "uint256 representing the amount owned by the passed address" + }, + "getApproved(uint256)": { + "details": "Gets the approved address for a token ID, or zero if no address set Reverts if the token ID does not exist.", + "params": { + "tokenId": "uint256 ID of the token to query the approval of" + }, + "return": "address currently approved for the given token ID" + }, + "isApprovedForAll(address,address)": { + "details": "Tells whether an operator is approved by a given owner.", + "params": { + "operator": "operator address which you want to query the approval of", + "owner": "owner address which you want to query the approval of" + }, + "return": "bool whether the given operator is approved by the given owner" + }, + "mintWithTokenURI(address,uint256,string)": { + "details": "Function to mint tokens.", + "params": { + "to": "The address that will receive the minted tokens.", + "tokenId": "The token id to mint.", + "tokenURI": "The token URI of the minted token." + }, + "return": "A boolean that indicates if the operation was successful." + }, + "name()": { + "details": "Gets the token name.", + "return": "string representing the token name" + }, + "ownerOf(uint256)": { + "details": "Gets the owner of the specified token ID.", + "params": { + "tokenId": "uint256 ID of the token to query the owner of" + }, + "return": "address currently marked as the owner of the given token ID" + }, + "safeTransferFrom(address,address,uint256)": { + "details": "Safely transfers the ownership of a given token ID to another address If the target address is a contract, it must implement `onERC721Received`, which is called upon a safe transfer, and return the magic value `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`; otherwise, the transfer is reverted. Requires the msg.sender to be the owner, approved, or operator", + "params": { + "from": "current owner of the token", + "to": "address to receive the ownership of the given token ID", + "tokenId": "uint256 ID of the token to be transferred" + } + }, + "safeTransferFrom(address,address,uint256,bytes)": { + "details": "Safely transfers the ownership of a given token ID to another address If the target address is a contract, it must implement `onERC721Received`, which is called upon a safe transfer, and return the magic value `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`; otherwise, the transfer is reverted. Requires the msg.sender to be the owner, approved, or operator", + "params": { + "_data": "bytes data to send along with a safe transfer check", + "from": "current owner of the token", + "to": "address to receive the ownership of the given token ID", + "tokenId": "uint256 ID of the token to be transferred" + } + }, + "setApprovalForAll(address,bool)": { + "details": "Sets or unsets the approval of a given operator An operator is allowed to transfer all tokens of the sender on their behalf.", + "params": { + "approved": "representing the status of the approval to be set", + "to": "operator address to set the approval" + } + }, + "supportsInterface(bytes4)": { + "details": "See `IERC165.supportsInterface`. * Time complexity O(1), guaranteed to always use less than 30 000 gas." + }, + "symbol()": { + "details": "Gets the token symbol.", + "return": "string representing the token symbol" + }, + "tokenByIndex(uint256)": { + "details": "Gets the token ID at a given index of all the tokens in this contract Reverts if the index is greater or equal to the total number of tokens.", + "params": { + "index": "uint256 representing the index to be accessed of the tokens list" + }, + "return": "uint256 token ID at the given index of the tokens list" + }, + "tokenOfOwnerByIndex(address,uint256)": { + "details": "Gets the token ID at a given index of the tokens list of the requested owner.", + "params": { + "index": "uint256 representing the index to be accessed of the requested tokens list", + "owner": "address owning the tokens list to be accessed" + }, + "return": "uint256 token ID at the given index of the tokens list owned by the requested address" + }, + "tokenURI(uint256)": { + "details": "Returns an URI for a given token ID. Throws if the token ID does not exist. May return an empty string.", + "params": { + "tokenId": "uint256 ID of the token to query" + } + }, + "totalSupply()": { + "details": "Gets the total amount of tokens stored by the contract.", + "return": "uint256 representing the total amount of tokens" + }, + "transferFrom(address,address,uint256)": { + "details": "Transfers the ownership of a given token ID to another address. Usage of this method is discouraged, use `safeTransferFrom` whenever possible. Requires the msg.sender to be the owner, approved, or operator.", + "params": { + "from": "current owner of the token", + "to": "address to receive the ownership of the given token ID", + "tokenId": "uint256 ID of the token to be transferred" + } + } + } + }, + "userdoc": { + "methods": {} + } +} diff --git a/test/test.py b/test/test.py new file mode 100644 index 00000000..930d15da --- /dev/null +++ b/test/test.py @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +import sys +import os +import logging + +from tools.test_runner import TestRunner + + +def main(): + argv, tests, prefix = [], None, 'tests=' + for argument in sys.argv: + if argument.startswith(prefix): + tests = argument[len(prefix):].split(',') + else: + argv.append(argument) + + if len(argv) < 2: + src_root = os.path.abspath(os.pardir) + else: + src_root = argv[1] + + test_runner = TestRunner(src_root, 'config.json', tests) + test_runner.run() + + +if __name__ == '__main__': + level = logging.INFO + # level = logging.DEBUG + logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=level) + main() diff --git a/test/test_cases/__init__.py b/test/test_cases/__init__.py new file mode 100644 index 00000000..6183891e --- /dev/null +++ b/test/test_cases/__init__.py @@ -0,0 +1,6 @@ +# from os.path import dirname, basename, isfile, join +from os import path +# +import glob +modules = glob.glob(path.join(path.dirname(__file__), "*.py")) +__all__ = [ path.basename(f)[:-3] for f in modules if path.isfile(f) and not f.endswith('__init__.py')] \ No newline at end of file diff --git a/test/test_cases/a_lot_of_send_ether_to_schain.py b/test/test_cases/a_lot_of_send_ether_to_schain.py new file mode 100644 index 00000000..0e26e7a7 --- /dev/null +++ b/test/test_cases/a_lot_of_send_ether_to_schain.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from tools.test_case import TestCase +from tools.test_pool import test_pool +import time + + +class ALotOfTransactionsSendEtherToSchain(TestCase): + def __init__(self, config): + super().__init__('Send ether a lot of times to schain', config) + + def _execute(self): + # + range_int = 5 + # + address = self.blockchain.key_to_address(self.config.mainnet_key) + balance = self.blockchain.get_balance_on_schain(address) + initial_balance = balance + # 2 ether (2 000 000 000 000 000 000 wei) + amount = 2 * 10 ** 18 + # + for x in range(range_int): + self.agent.transfer_eth_from_mainnet_to_schain(self.config.mainnet_key, + self.config.schain_key, + amount, + self.timeout) + a = 23 + # + balance = self.blockchain.get_balance_on_schain(address) + if balance == initial_balance + range_int * amount: + self._mark_passed() + +test_pool.register_test(ALotOfTransactionsSendEtherToSchain) + +# singe trans +# Gas usage: + +# 3 trans +# Gas usage: 111058 +# Gas usage: 66122 +# Gas usage: 66122 + +# 7 trans +# Gas usage: 111058 diff --git a/test/test_cases/load_send_ether_from_mainnet_to_schain_and_back.py b/test/test_cases/load_send_ether_from_mainnet_to_schain_and_back.py new file mode 100644 index 00000000..51839fe9 --- /dev/null +++ b/test/test_cases/load_send_ether_from_mainnet_to_schain_and_back.py @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from logging import debug + +from tools.test_case import TestCase +from tools.test_pool import test_pool +from time import sleep + + +class SendEtherFromSchainToMainnetAndBack(TestCase): + + def __init__(self, config): + super().__init__('load_send_ether_from_mainnet_to_schain_and_back', config) + + def _execute(self): + amountRecharge = 2 * 10 ** 18 + self.blockchain.recharge_user_wallet(self.config.mainnet_key, self.config.schain_name, amountRecharge) + sleep( 5 ) + range_int = 5 + # ETH + eth_amount = 12 * 10 ** 18 + address = self.blockchain.key_to_address(self.config.mainnet_key) + # transfer to schain + self.agent.transfer_eth_from_mainnet_to_schain(self.config.mainnet_key, + self.config.schain_key, + eth_amount, + self.timeout) + sleep( 5 ) + # + balance = self.blockchain.get_balance_on_schain(address) + initial_balance = balance + # 2 ether (2 000 000 000 000 000 000 wei) + amount = 2 * 10 ** 18 + # 60 finney back because when we send on mainnet we should be able to cover gas fee on gasPrice 200 Gwei + amount_from_schain = 7 * 10 ** 16 + # + self.blockchain.set_time_limit_per_message(self.config.schain_key, 0) + for x in range(range_int): + # transfer to schain + self.agent.transfer_eth_from_mainnet_to_schain(self.config.mainnet_key, + self.config.schain_key, + amount, + self.timeout) + sleep( 5 ) + # back to mainnet + self.agent.transfer_eth_from_schain_to_mainnet(self.config.mainnet_key, + self.config.schain_key, + amount_from_schain, + self.timeout) + sleep( 5 ) + self.blockchain.get_balance_on_schain(address) + a = 0 + # + balance = self.blockchain.get_balance_on_schain(address) + print( 'Real balance.......', balance ) + needed_balance = initial_balance + range_int * amount - range_int * amount_from_schain + print( 'Needed balance.....', needed_balance ) + + res = initial_balance - range_int * amount + if balance == needed_balance: + print( 'Passed.............', 'YES!' ) + self._mark_passed() + else: + print( 'Passed.............', 'NO(' ) + + +test_pool.register_test(SendEtherFromSchainToMainnetAndBack) diff --git a/test/test_cases/send_erc1155_batch_from_mainnet_to_schain.py b/test/test_cases/send_erc1155_batch_from_mainnet_to_schain.py new file mode 100644 index 00000000..e7da44e8 --- /dev/null +++ b/test/test_cases/send_erc1155_batch_from_mainnet_to_schain.py @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from time import sleep + +from tools.test_case import TestCase +from tools.test_pool import test_pool + + +class SendERC1155BatchToSchain(TestCase): + erc1155 = None + tokenIds = [1, 2, 3] + tokenAmounts = [3, 2, 1] + + def __init__(self, config): + super().__init__('Send ERC1155 Batch to schain', config) + + def _prepare(self): + sleep( 5 ) + self.erc1155 = self.blockchain.deploy_erc1155_on_mainnet(self.config.mainnet_key, 'elv1155') + + address = self.blockchain.key_to_address(self.config.mainnet_key) + mint_txn = self.erc1155.functions.mintBatch(address, self.tokenIds, self.tokenAmounts, "0x")\ + .buildTransaction({ + 'gas': 8000000, + 'nonce': self.blockchain.get_transactions_count_on_mainnet(address)}) + + signed_txn = self.blockchain.web3_mainnet.eth.account.signTransaction(mint_txn, + private_key=self.config.mainnet_key) + self.blockchain.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + self.blockchain.disableWhitelistERC1155(self.config.mainnet_key, self.config.schain_name) + self.blockchain.enableAutomaticDeployERC1155(self.config.schain_key, "Mainnet") + + def _execute(self): + + self.agent.transfer_erc1155_batch_from_mainnet_to_schain( + self.erc1155, + self.config.mainnet_key, + self.config.schain_key, + self.tokenIds, + self.tokenAmounts, + 0, + self.timeout + ) + + erc1155 = self.blockchain.get_erc1155_on_schain("Mainnet", self.erc1155.address) + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + new_amounts = erc1155.functions.balanceOfBatch([destination_address] * len(self.tokenIds), self.tokenIds).call() + if self.tokenAmounts == new_amounts: + self._mark_passed() + +test_pool.register_test(SendERC1155BatchToSchain) diff --git a/test/test_cases/send_erc1155_batch_from_schain_to_mainnet.py b/test/test_cases/send_erc1155_batch_from_schain_to_mainnet.py new file mode 100644 index 00000000..c8fc21f1 --- /dev/null +++ b/test/test_cases/send_erc1155_batch_from_schain_to_mainnet.py @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from time import sleep +from logging import error + +from tools.test_case import TestCase +from tools.test_pool import test_pool + + +class SendERC1155BatchToMainnet(TestCase): + erc1155 = None + erc1155_clone = None + token_ids = [1, 2, 5, 6] + token_amounts = [4, 4, 4, 99999] + + def __init__(self, config): + super().__init__('Send ERC1155 Batch from schain to mainnet', config) + + def _prepare(self): + sleep( 5 ) + amountRecharge = 2 * 10 ** 18 + self.blockchain.recharge_user_wallet(self.config.mainnet_key, self.config.schain_name, amountRecharge) + sleep( 5 ) + # deploy token + self.erc1155 = self.blockchain.deploy_erc1155_on_mainnet(self.config.mainnet_key, 'elv1155') + # mint + address = self.blockchain.key_to_address(self.config.mainnet_key) + mint_txn = self.erc1155.functions.mintBatch(address, self.token_ids, self.token_amounts, "0x")\ + .buildTransaction({ + 'gas': 8000000, + 'nonce': self.blockchain.get_transactions_count_on_mainnet(address)}) + signed_txn = self.blockchain.web3_mainnet.eth.account\ + .signTransaction(mint_txn, private_key=self.config.mainnet_key) + self.blockchain.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + self.blockchain.disableWhitelistERC1155(self.config.mainnet_key, self.config.schain_name) + self.blockchain.enableAutomaticDeployERC1155(self.config.schain_key, "Mainnet") + # send to schain + self.agent.transfer_erc1155_batch_from_mainnet_to_schain(self.erc1155, + self.config.mainnet_key, + self.config.schain_key, + self.token_ids, + self.token_amounts, + self.timeout) + sleep( 5 ) + amount_eth = 90 * 10 ** 15 + self.agent.transfer_eth_from_mainnet_to_schain(self.config.mainnet_key, + self.config.schain_key, + amount_eth, + self.timeout) + sleep( 5 ) + self.erc1155_clone = self.blockchain.get_erc1155_on_schain("Mainnet", self.erc1155.address) + + def _execute(self): + source_address = self.blockchain.key_to_address(self.config.mainnet_key) + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + + if self.erc1155_clone.functions.balanceOfBatch([source_address] * len(self.token_ids), self.token_ids).call() != self.token_amounts: + error("Token was not send") + return + sleep( 5 ) + self.agent.transfer_erc1155_batch_from_schain_to_mainnet( + self.erc1155_clone, + self.erc1155, + self.config.mainnet_key, + self.config.schain_key, + self.token_ids, + self.token_amounts, + 6 * 10 ** 16, + self.timeout + ) + # + # erc1155 = self.blockchain.get_erc1155_on_mainnet(self.token_id) + # new_owner_address = erc1155.functions.ownerOf(self.token_id).call() + new_amounts = self.erc1155.functions.balanceOfBatch([destination_address] * len(self.token_ids), self.token_ids).call() + if self.token_amounts == new_amounts: + self._mark_passed() + + +test_pool.register_test(SendERC1155BatchToMainnet) diff --git a/test/test_cases/send_erc1155_from_mainnet_to_schain.py b/test/test_cases/send_erc1155_from_mainnet_to_schain.py new file mode 100644 index 00000000..5283fee6 --- /dev/null +++ b/test/test_cases/send_erc1155_from_mainnet_to_schain.py @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from time import sleep + +from tools.test_case import TestCase +from tools.test_pool import test_pool + + +class SendERC1155ToSchain(TestCase): + erc1155 = None + tokenId = 1 + tokenAmount = 3 + + def __init__(self, config): + super().__init__('Send ERC1155 to schain', config) + + def _prepare(self): + sleep( 5 ) + self.erc1155 = self.blockchain.deploy_erc1155_on_mainnet(self.config.mainnet_key, 'elv1155') + + address = self.blockchain.key_to_address(self.config.mainnet_key) + mint_txn = self.erc1155.functions.mint(address, self.tokenId, self.tokenAmount, "0x")\ + .buildTransaction({ + 'gas': 8000000, + 'nonce': self.blockchain.get_transactions_count_on_mainnet(address)}) + + signed_txn = self.blockchain.web3_mainnet.eth.account.signTransaction(mint_txn, + private_key=self.config.mainnet_key) + self.blockchain.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + self.blockchain.disableWhitelistERC1155(self.config.mainnet_key, self.config.schain_name) + self.blockchain.enableAutomaticDeployERC1155(self.config.schain_key, "Mainnet") + + def _execute(self): + + self.agent.transfer_erc1155_from_mainnet_to_schain( + self.erc1155, + self.config.mainnet_key, + self.config.schain_key, + self.tokenId, + self.tokenAmount, + 0, + self.timeout + ) + + erc1155 = self.blockchain.get_erc1155_on_schain("Mainnet", self.erc1155.address) + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + new_amount = erc1155.functions.balanceOf(destination_address, self.tokenId).call() + if self.tokenAmount == new_amount: + self._mark_passed() + +test_pool.register_test(SendERC1155ToSchain) diff --git a/test/test_cases/send_erc1155_from_schain_to_mainnet.py b/test/test_cases/send_erc1155_from_schain_to_mainnet.py new file mode 100644 index 00000000..cd609e57 --- /dev/null +++ b/test/test_cases/send_erc1155_from_schain_to_mainnet.py @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from time import sleep +from logging import error + +from tools.test_case import TestCase +from tools.test_pool import test_pool + + +class Senderc1155ToMainnet(TestCase): + erc1155 = None + erc1155_clone = None + token_id = 1 + token_amount = 4 + + def __init__(self, config): + super().__init__('Send ERC1155 from schain to mainnet', config) + + def _prepare(self): + sleep( 5 ) + amountRecharge = 2 * 10 ** 18 + self.blockchain.recharge_user_wallet(self.config.mainnet_key, self.config.schain_name, amountRecharge) + sleep( 5 ) + # deploy token + self.erc1155 = self.blockchain.deploy_erc1155_on_mainnet(self.config.mainnet_key, 'elv1155') + # mint + address = self.blockchain.key_to_address(self.config.mainnet_key) + mint_txn = self.erc1155.functions.mint(address, self.token_id, self.token_amount, "0x")\ + .buildTransaction({ + 'gas': 8000000, + 'nonce': self.blockchain.get_transactions_count_on_mainnet(address)}) + signed_txn = self.blockchain.web3_mainnet.eth.account\ + .signTransaction(mint_txn, private_key=self.config.mainnet_key) + self.blockchain.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + self.blockchain.disableWhitelistERC1155(self.config.mainnet_key, self.config.schain_name) + self.blockchain.enableAutomaticDeployERC1155(self.config.schain_key, "Mainnet") + # send to schain + self.agent.transfer_erc1155_from_mainnet_to_schain(self.erc1155, + self.config.mainnet_key, + self.config.schain_key, + self.token_id, + self.token_amount, + self.timeout) + sleep( 5 ) + amount_eth = 90 * 10 ** 15 + self.agent.transfer_eth_from_mainnet_to_schain(self.config.mainnet_key, + self.config.schain_key, + amount_eth, + self.timeout) + sleep( 5 ) + self.erc1155_clone = self.blockchain.get_erc1155_on_schain("Mainnet", self.erc1155.address) + + def _execute(self): + source_address = self.blockchain.key_to_address(self.config.mainnet_key) + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + + if self.erc1155_clone.functions.balanceOf(source_address, self.token_id).call() != self.token_amount: + error("Token was not send") + return + sleep( 5 ) + self.agent.transfer_erc1155_from_schain_to_mainnet( + self.erc1155_clone, + self.erc1155, + self.config.mainnet_key, + self.config.schain_key, + self.token_id, + self.token_amount, + 6 * 10 ** 16, + self.timeout + ) + # + # erc1155 = self.blockchain.get_erc1155_on_mainnet(self.token_id) + # new_owner_address = erc1155.functions.ownerOf(self.token_id).call() + new_amount = self.erc1155.functions.balanceOf(destination_address, self.token_id).call() + if self.token_amount == new_amount: + self._mark_passed() + + +test_pool.register_test(Senderc1155ToMainnet) diff --git a/test/test_cases/send_erc20_from_mainnet_to_schain.py b/test/test_cases/send_erc20_from_mainnet_to_schain.py new file mode 100644 index 00000000..0459cb4b --- /dev/null +++ b/test/test_cases/send_erc20_from_mainnet_to_schain.py @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from time import sleep + +from tools.test_case import TestCase +from tools.test_pool import test_pool + + +class SendERC20ToSchain(TestCase): + erc20 = None + + def __init__(self, config): + super().__init__('Send ERC20 to schain', config) + + def _prepare(self): + sleep( 5 ) + self.erc20 = self.blockchain.deploy_erc20_on_mainnet(self.config.mainnet_key, 'D2-Token', 'D2', 100) + + address = self.blockchain.key_to_address(self.config.mainnet_key) + mint_txn = self.erc20.functions.mint(address, 1)\ + .buildTransaction({ + 'gas': 8000000, + 'nonce': self.blockchain.get_transactions_count_on_mainnet(address)}) + + signed_txn = self.blockchain.web3_mainnet.eth.account.signTransaction(mint_txn, + private_key=self.config.mainnet_key) + self.blockchain.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + self.blockchain.disableWhitelistERC20(self.config.mainnet_key, self.config.schain_name) + self.blockchain.enableAutomaticDeployERC20(self.config.schain_key, "Mainnet") + + def _execute(self): + amount = 1 + self.agent.transfer_erc20_from_mainnet_to_schain( + self.erc20, + self.config.mainnet_key, + self.config.schain_key, + amount, + 0, + self.timeout + ) + + erc20 = self.blockchain.get_erc20_on_schain("Mainnet", self.erc20.address) + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + balance = erc20.functions.balanceOf(destination_address).call() + if balance == amount: + self._mark_passed() + +test_pool.register_test(SendERC20ToSchain) diff --git a/test/test_cases/send_erc20_from_schain_to_mainnet.py b/test/test_cases/send_erc20_from_schain_to_mainnet.py new file mode 100644 index 00000000..48e216c8 --- /dev/null +++ b/test/test_cases/send_erc20_from_schain_to_mainnet.py @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from time import sleep +from logging import debug, error + +from tools.test_case import TestCase +from tools.test_pool import test_pool + + +class SendERC20ToMainnet(TestCase): + erc20 = None + erc20_clone = None + amount = 4 + # index of token in token_manager_erc20.sol + index = 1 + + def __init__(self, config): + super().__init__('Send ERC20 from schain to mainnet', config) + + def _prepare(self): + sleep( 5 ) + amountRecharge = 2 * 10 ** 18 + self.blockchain.recharge_user_wallet(self.config.mainnet_key, self.config.schain_name, amountRecharge) + sleep( 5 ) + + # deploy token + + self.erc20 = self.blockchain.deploy_erc20_on_mainnet(self.config.mainnet_key, 'D2-Token', 'D2', 100) + + # mint + + address = self.blockchain.key_to_address(self.config.mainnet_key) + mint_txn = self.erc20.functions.mint(address, self.amount)\ + .buildTransaction({ + 'gas': 8000000, + 'nonce': self.blockchain.get_transactions_count_on_mainnet(address)}) + + signed_txn = self.blockchain.web3_mainnet.eth.account.signTransaction(mint_txn, + private_key=self.config.mainnet_key) + self.blockchain.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + self.blockchain.disableWhitelistERC20(self.config.mainnet_key, self.config.schain_name) + self.blockchain.enableAutomaticDeployERC20(self.config.schain_key, "Mainnet") + + # send to schain + + self.agent.transfer_erc20_from_mainnet_to_schain(self.erc20, + self.config.mainnet_key, + self.config.schain_key, + self.amount, + self.timeout) + sleep( 5 ) + + amount_of_eth = 90 * 10 ** 15 + + self.agent.transfer_eth_from_mainnet_to_schain(self.config.mainnet_key, + self.config.schain_key, + amount_of_eth, + self.timeout) + sleep( 5 ) + + self.erc20_clone = self.blockchain.get_erc20_on_schain("Mainnet", self.erc20.address) + + def _execute(self): + source_address = self.blockchain.key_to_address(self.config.mainnet_key) + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + + if self.erc20_clone.functions.balanceOf(source_address).call() < self.amount: + error("Not enough tokens to send") + return + balance = self.erc20.functions.balanceOf(destination_address).call() + + self.agent.transfer_erc20_from_schain_to_mainnet( + self.erc20_clone, # token + self.erc20, # token on mainnet + self.config.mainnet_key, # from + self.config.schain_key, # to + (self.amount - 2), # 2 tokens + 6 * 10 ** 16, + self.timeout + ) + + # if self.erc20.functions.balanceOf(destination_address).call() == balance + self.amount: + if self.erc20.functions.balanceOf(destination_address).call() == (self.amount - 2): + self._mark_passed() + + +test_pool.register_test(SendERC20ToMainnet) diff --git a/test/test_cases/send_erc721_from_mainnet_to_schain.py b/test/test_cases/send_erc721_from_mainnet_to_schain.py new file mode 100644 index 00000000..cb610ff5 --- /dev/null +++ b/test/test_cases/send_erc721_from_mainnet_to_schain.py @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from time import sleep +from logging import debug + +from tools.test_case import TestCase +from tools.test_pool import test_pool + + +class SendERC721ToSchain(TestCase): + erc721 = None + tokenId = 1 + + def __init__(self, config): + super().__init__('Send ERC721 to schain', config) + + def _prepare(self): + sleep( 5 ) + self.erc721 = self.blockchain.deploy_erc721_on_mainnet(self.config.mainnet_key, 'elv721', 'ELV') + + address = self.blockchain.key_to_address(self.config.mainnet_key) + mint_txn = self.erc721.functions.mint(address, self.tokenId)\ + .buildTransaction({ + 'gas': 8000000, + 'nonce': self.blockchain.get_transactions_count_on_mainnet(address)}) + + signed_txn = self.blockchain.web3_mainnet.eth.account.signTransaction(mint_txn, + private_key=self.config.mainnet_key) + self.blockchain.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + self.blockchain.disableWhitelistERC721(self.config.mainnet_key, self.config.schain_name) + self.blockchain.enableAutomaticDeployERC721(self.config.schain_key, "Mainnet") + + def _execute(self): + + self.agent.transfer_erc721_from_mainnet_to_schain( + self.erc721, + self.config.mainnet_key, + self.config.schain_key, + self.tokenId, + 0, + self.timeout + ) + + erc721 = self.blockchain.get_erc721_on_schain("Mainnet", self.erc721.address) + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + new_owner_address = erc721.functions.ownerOf(self.tokenId).call() + if destination_address == new_owner_address: + self._mark_passed() + +test_pool.register_test(SendERC721ToSchain) diff --git a/test/test_cases/send_erc721_from_schain_to_mainnet.py b/test/test_cases/send_erc721_from_schain_to_mainnet.py new file mode 100644 index 00000000..957ac7c4 --- /dev/null +++ b/test/test_cases/send_erc721_from_schain_to_mainnet.py @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from time import sleep +from logging import debug, error + +from tools.test_case import TestCase +from tools.test_pool import test_pool + + +class Senderc721ToMainnet(TestCase): + erc721 = None + erc721_clone = None + token_id = 1 + + def __init__(self, config): + super().__init__('Send ERC721 from schain to mainnet', config) + + def _prepare(self): + sleep( 5 ) + amountRecharge = 2 * 10 ** 18 + self.blockchain.recharge_user_wallet(self.config.mainnet_key, self.config.schain_name, amountRecharge) + sleep( 5 ) + # deploy token + self.erc721 = self.blockchain.deploy_erc721_on_mainnet(self.config.mainnet_key, 'elv721', 'ELV') + # mint + address = self.blockchain.key_to_address(self.config.mainnet_key) + mint_txn = self.erc721.functions.mint(address, self.token_id)\ + .buildTransaction({ + 'gas': 8000000, + 'nonce': self.blockchain.get_transactions_count_on_mainnet(address)}) + signed_txn = self.blockchain.web3_mainnet.eth.account\ + .signTransaction(mint_txn, private_key=self.config.mainnet_key) + self.blockchain.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + self.blockchain.disableWhitelistERC721(self.config.mainnet_key, self.config.schain_name) + self.blockchain.enableAutomaticDeployERC721(self.config.schain_key, "Mainnet") + # send to schain + self.agent.transfer_erc721_from_mainnet_to_schain(self.erc721, + self.config.mainnet_key, + self.config.schain_key, + self.token_id, + self.timeout) + sleep( 5 ) + # + amount_eth = 90 * 10 ** 15 + # + self.agent.transfer_eth_from_mainnet_to_schain(self.config.mainnet_key, + self.config.schain_key, + amount_eth, + self.timeout) + + # + sleep( 5 ) + self.erc721_clone = self.blockchain.get_erc721_on_schain("Mainnet", self.erc721.address) + + def _execute(self): + source_address = self.blockchain.key_to_address(self.config.mainnet_key) + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + + if self.erc721_clone.functions.ownerOf(self.token_id).call() != source_address: + error("Token was not send") + return + # + sleep( 5 ) + self.agent.transfer_erc721_from_schain_to_mainnet( + self.erc721_clone, + self.erc721, + self.config.mainnet_key, + self.config.schain_key, + self.token_id, + 6 * 10 ** 16, + self.timeout + ) + # + # erc721 = self.blockchain.get_erc721_on_mainnet(self.token_id) + # new_owner_address = erc721.functions.ownerOf(self.token_id).call() + new_owner_address = self.erc721.functions.ownerOf(self.token_id).call() + if destination_address == new_owner_address: + self._mark_passed() + + +test_pool.register_test(Senderc721ToMainnet) diff --git a/test/test_cases/send_ether_from_schain_to_mainnet.py b/test/test_cases/send_ether_from_schain_to_mainnet.py new file mode 100644 index 00000000..84b1e173 --- /dev/null +++ b/test/test_cases/send_ether_from_schain_to_mainnet.py @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from logging import debug + +from tools.test_case import TestCase +from tools.test_pool import test_pool + +from time import sleep + +class SendEtherToMainnet(TestCase): + amount = 7 * 10 ** 16 # 60 finney + from_key = None + to_key = None + + def __init__(self, config): + super().__init__('Send ether from schain to mainnet', config) + + def _prepare(self): + sleep( 5 ) + amountRecharge = 2 * 10 ** 18 + self.blockchain.recharge_user_wallet(self.config.mainnet_key, self.config.schain_name, amountRecharge) + sleep( 5 ) + + source_address = self.blockchain.key_to_address(self.config.schain_key) + if self.blockchain.get_balance_on_schain(source_address) < self.amount: + self.agent.transfer_eth_from_mainnet_to_schain(self.config.mainnet_key, + self.config.schain_key, + self.amount, + timeout=self.timeout) + min_transaction_fee = 21 * 10 ** 15 + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + if self.blockchain.get_balance_on_mainnet(destination_address) < min_transaction_fee: + self.blockchain.send_ether_on_mainnet(self.config.mainnet_key, self.config.mainnet_key, min_transaction_fee) + sleep( 5 ) + + def _execute(self): + source_address = self.blockchain.key_to_address(self.config.mainnet_key) + if self.blockchain.get_balance_on_schain(source_address) < self.amount: + return + + debug('Balance on schain:', self.blockchain.get_balance_on_schain(source_address)) + + + destination_address = self.blockchain.key_to_address(self.config.mainnet_key) + balance = self.blockchain.get_balance_on_mainnet(destination_address) + + debug('Destination balance:', balance) + + self.agent.transfer_eth_from_schain_to_mainnet(self.config.mainnet_key, + self.config.schain_key, + self.amount, + self.timeout) + sleep( 5 ) + + transaction_fee = 6 * 10 ** 16 + approximate_gas_spends = 3 * 10 ** 15 + extra_subtract_value = 1 * 10 ** 17 + + real_balance = self.blockchain.get_balance_on_mainnet(destination_address) + print( 'Real balance.......', real_balance ) + expected_balance = balance + self.amount - transaction_fee - approximate_gas_spends - extra_subtract_value + print( 'Expected balance...', expected_balance ) + + if real_balance > expected_balance: + print( 'Passed.............', 'YES!' ) + self._mark_passed() + else: + print( 'Passed.............', 'NO(' ) + + +test_pool.register_test(SendEtherToMainnet) diff --git a/test/test_cases/send_ether_to_schain.py b/test/test_cases/send_ether_to_schain.py new file mode 100644 index 00000000..007fa19c --- /dev/null +++ b/test/test_cases/send_ether_to_schain.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from tools.test_case import TestCase +from tools.test_pool import test_pool + + +class SendEtherToSchain(TestCase): + def __init__(self, config): + super().__init__('Send ether to schain', config) + + def _execute(self): + address = self.blockchain.key_to_address(self.config.mainnet_key) + balance = self.blockchain.get_balance_on_schain(address) + initial_balance = balance + amount = 2 * 10 ** 15 # 2 finney + + self.agent.transfer_eth_from_mainnet_to_schain(self.config.mainnet_key, + self.config.schain_key, + amount, + self.timeout) + + balance = self.blockchain.get_balance_on_schain(address) + if balance == initial_balance + amount: + self._mark_passed() + +test_pool.register_test(SendEtherToSchain) diff --git a/test/tools/blockchain.py b/test/tools/blockchain.py new file mode 100644 index 00000000..4b0da570 --- /dev/null +++ b/test/tools/blockchain.py @@ -0,0 +1,396 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from web3 import Web3, HTTPProvider +import json +from eth_account import Account +from time import sleep + +class BlockChain: + config = None + web3_mainnet = None + web3_schain = None + + + def __init__(self, config): + self.config = config + self.web3_mainnet = Web3(HTTPProvider(self.config.mainnet_rpc_url)) + self.web3_schain = Web3(HTTPProvider(self.config.schain_rpc_url)) + if not self.config.user_key: + self.config.user_key = Account.create().privateKey.hex()[2:] + + def get_balance_on_schain(self, address): + eth_token = self._get_contract_on_schain('eth_erc20') + return eth_token.functions.balanceOf(address).call() + + def get_balance_on_mainnet(self, address): + return self.web3_mainnet.eth.getBalance(address) + + @staticmethod + def key_to_address(key): + return Account.privateKeyToAccount(key).address + + def wei_to_bigger(self, amount): + units = {'wei': 1, + 'babbage': 10 ** 3, + 'lovelace': 10 ** 6, + 'shannon': 10 ** 9, + 'szabo': 10 ** 12, + 'finney': 10 ** 15, + 'ether': 10 ** 18} + + unit_name, new_amount = 'wei', amount + for unit, value in units.items(): + if amount % value == 0: + if amount // value < new_amount: + unit_name = unit + new_amount = amount // value + return new_amount, unit_name + + def get_approved_amount(self, address): + deposit_box_eth = self._get_contract_on_mainnet('deposit_box_eth') + return deposit_box_eth.functions.approveTransfers(address).call() + + def enableAutomaticDeployERC20(self, from_key, schainName): + sender_address = self.key_to_address(from_key) + token_manager_erc20 = self._get_contract_on_schain('token_manager_erc20') + enable = token_manager_erc20.encodeABI(fn_name="enableAutomaticDeploy", args=[]) + signed_txn = self.web3_schain.eth.account.signTransaction(dict( + nonce=self.web3_schain.eth.getTransactionCount(sender_address), + gasPrice=self.web3_schain.eth.gasPrice, + gas=200000, + to=token_manager_erc20.address, + value=0, + data = enable + ), + from_key) + self.web3_schain.eth.sendRawTransaction(signed_txn.rawTransaction) + + def enableAutomaticDeployERC721(self, from_key, schainName): + sender_address = self.key_to_address(from_key) + token_manager_erc721 = self._get_contract_on_schain('token_manager_erc721') + enable = token_manager_erc721.encodeABI(fn_name="enableAutomaticDeploy", args=[]) + signed_txn = self.web3_schain.eth.account.signTransaction(dict( + nonce=self.web3_schain.eth.getTransactionCount(sender_address), + gasPrice=self.web3_schain.eth.gasPrice, + gas=200000, + to=token_manager_erc721.address, + value=0, + data = enable + ), + from_key) + self.web3_schain.eth.sendRawTransaction(signed_txn.rawTransaction) + + def enableAutomaticDeployERC1155(self, from_key, schainName): + sender_address = self.key_to_address(from_key) + token_manager_erc1155 = self._get_contract_on_schain('token_manager_erc1155') + enable = token_manager_erc1155.encodeABI(fn_name="enableAutomaticDeploy", args=[]) + signed_txn = self.web3_schain.eth.account.signTransaction(dict( + nonce=self.web3_schain.eth.getTransactionCount(sender_address), + gasPrice=self.web3_schain.eth.gasPrice, + gas=200000, + to=token_manager_erc1155.address, + value=0, + data = enable + ), + from_key) + self.web3_schain.eth.sendRawTransaction(signed_txn.rawTransaction) + + def disableWhitelistERC20(self, from_key, schainName): + sender_address = self.key_to_address(from_key) + deposit_box_erc20 = self._get_contract_on_mainnet('deposit_box_erc20') + disable = deposit_box_erc20.encodeABI(fn_name="disableWhitelist", args=[schainName]) + signed_txn = self.web3_mainnet.eth.account.signTransaction(dict( + nonce=self.web3_mainnet.eth.getTransactionCount(sender_address), + gasPrice=self.web3_mainnet.eth.gasPrice, + gas=200000, + to=deposit_box_erc20.address, + value=0, + data = disable + ), + from_key) + self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + + def disableWhitelistERC721(self, from_key, schainName): + sender_address = self.key_to_address(from_key) + deposit_box_erc721 = self._get_contract_on_mainnet('deposit_box_erc721') + disable = deposit_box_erc721.encodeABI(fn_name="disableWhitelist", args=[schainName]) + signed_txn = self.web3_mainnet.eth.account.signTransaction(dict( + nonce=self.web3_mainnet.eth.getTransactionCount(sender_address), + gasPrice=self.web3_mainnet.eth.gasPrice, + gas=200000, + to=deposit_box_erc721.address, + value=0, + data = disable + ), + from_key) + self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + + def disableWhitelistERC1155(self, from_key, schainName): + sender_address = self.key_to_address(from_key) + deposit_box_erc1155 = self._get_contract_on_mainnet('deposit_box_erc1155') + disable = deposit_box_erc1155.encodeABI(fn_name="disableWhitelist", args=[schainName]) + signed_txn = self.web3_mainnet.eth.account.signTransaction(dict( + nonce=self.web3_mainnet.eth.getTransactionCount(sender_address), + gasPrice=self.web3_mainnet.eth.gasPrice, + gas=200000, + to=deposit_box_erc1155.address, + value=0, + data = disable + ), + from_key) + self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + + def addERC20TokenByOwner(self, from_key, schainName, erc20Address): + sender_address = self.key_to_address(from_key) + deposit_box_erc20 = self._get_contract_on_mainnet('deposit_box_erc20') + disable = deposit_box_erc20.encodeABI(fn_name="addERC20TokenByOwner", args=[schainName, erc20Address]) + signed_txn = self.web3_mainnet.eth.account.signTransaction(dict( + nonce=self.web3_mainnet.eth.getTransactionCount(sender_address), + gasPrice=self.web3_mainnet.eth.gasPrice, + gas=200000, + to=deposit_box_erc20.address, + value=0, + data = disable + ), + from_key) + self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + + def addERC721TokenByOwner(self, from_key, schainName, erc20Address): + sender_address = self.key_to_address(from_key) + deposit_box_erc721 = self._get_contract_on_mainnet('deposit_box_erc721') + disable = deposit_box_erc721.encodeABI(fn_name="addERC721TokenByOwner", args=[schainName, erc20Address]) + signed_txn = self.web3_mainnet.eth.account.signTransaction(dict( + nonce=self.web3_mainnet.eth.getTransactionCount(sender_address), + gasPrice=self.web3_mainnet.eth.gasPrice, + gas=200000, + to=deposit_box_erc721.address, + value=0, + data = disable + ), + from_key) + self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + + def addERC1155TokenByOwner(self, from_key, schainName, erc20Address): + sender_address = self.key_to_address(from_key) + deposit_box_erc1155 = self._get_contract_on_mainnet('deposit_box_erc1155') + disable = deposit_box_erc1155.encodeABI(fn_name="addERC1155TokenByOwner", args=[schainName, erc20Address]) + signed_txn = self.web3_mainnet.eth.account.signTransaction(dict( + nonce=self.web3_mainnet.eth.getTransactionCount(sender_address), + gasPrice=self.web3_mainnet.eth.gasPrice, + gas=200000, + to=deposit_box_erc1155.address, + value=0, + data = disable + ), + from_key) + self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + + def add_eth_cost(self, from_key, amount): + sender_address = self.key_to_address(from_key) + token_manager = self._get_contract_on_schain('token_manager') + add_eth_cost_encode_abi = token_manager.encodeABI(fn_name="addEthCostWithoutAddress", args=[amount]) + signed_txn = self.web3_schain.eth.account.signTransaction(dict( + nonce=self.web3_schain.eth.getTransactionCount(sender_address), + gasPrice=self.web3_schain.eth.gasPrice, + gas=200000, + to=token_manager.address, + value=0, + data = add_eth_cost_encode_abi + ), + from_key) + self.web3_schain.eth.sendRawTransaction(signed_txn.rawTransaction) + + def send_ether_on_mainnet(self, from_key, to_key, amount_wei): + sender_address = self.key_to_address(from_key) + recipient_address = self.key_to_address(to_key) + signed_txn = self.web3_mainnet.eth.account.signTransaction(dict( + nonce=self.web3_mainnet.eth.getTransactionCount(sender_address), + gasPrice=self.web3_mainnet.eth.gasPrice, + gas=100000, + to=recipient_address, + value=amount_wei + ), + from_key) + + self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + + def recharge_user_wallet(self, from_key, schainName, amount_wei): + sender_address = self.key_to_address(from_key) + community_pool = self._get_contract_on_mainnet('community_pool') + recharge_abi = community_pool.encodeABI(fn_name="rechargeUserWallet", args=[schainName, sender_address]) + signed_txn = self.web3_mainnet.eth.account.signTransaction(dict( + nonce=self.web3_mainnet.eth.getTransactionCount(sender_address), + gasPrice=self.web3_mainnet.eth.gasPrice, + gas=200000, + to=community_pool.address, + value=amount_wei, + data = recharge_abi + ), + from_key) + self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction) + + def set_time_limit_per_message(self, from_key, time_limit): + sender_address = self.key_to_address(from_key) + community_locker = self._get_contract_on_schain('community_locker') + time_limit_abi = community_locker.encodeABI(fn_name="setTimeLimitPerMessage", args=["Mainnet", time_limit]) + signed_txn = self.web3_schain.eth.account.signTransaction(dict( + nonce=self.web3_schain.eth.getTransactionCount(sender_address), + gasPrice=self.web3_schain.eth.gasPrice, + gas=200000, + to=community_locker.address, + value=0, + data=time_limit_abi + ), + from_key) + self.web3_schain.eth.sendRawTransaction(signed_txn.rawTransaction) + + def deploy_erc20_on_mainnet(self, private_key, name, symbol, decimals): + return self._deploy_contract_to_mainnet(self.config.test_root + '/resources/ERC20MintableDetailed.json', + [name, symbol, decimals], + private_key) + def deploy_erc721_on_mainnet(self, private_key, name, symbol): + return self._deploy_contract_to_mainnet(self.config.test_root + '/resources/ERC721FullMetadataMintable.json', + [name, symbol], + private_key) + + def deploy_erc1155_on_mainnet(self, private_key, uri): + return self._deploy_contract_to_mainnet(self.config.test_root + '/resources/ERC1155BurnableMintable.json', + [uri], + private_key) + + def get_transactions_count_on_mainnet(self, address): + return self.web3_mainnet.eth.getTransactionCount(address) + + def get_erc20_on_schain(self, schain_name, erc20_address_mainnet): + lock_erc20 = self._get_contract_on_schain('token_manager_erc20') + mainnet_hash = Web3.solidityKeccak(['string'], ["Mainnet"]) + erc20_address = lock_erc20.functions.clonesErc20(mainnet_hash, erc20_address_mainnet).call() + if erc20_address == '0x0000000000000000000000000000000000000000': + raise ValueError('No such token') + with open(self.config.proxy_root + '/artifacts/contracts/schain/tokens/ERC20OnChain.sol/ERC20OnChain.json') as erc20_on_chain_file: + erc20_on_chain_json = json.load(erc20_on_chain_file) + return self.web3_schain.eth.contract(address=erc20_address, abi=erc20_on_chain_json['abi']) + + def get_erc721_on_schain(self, schain_name, erc721_address_mainnet): + lock_erc721 = self._get_contract_on_schain('token_manager_erc721') + mainnet_hash = Web3.solidityKeccak(['string'], ["Mainnet"]) + erc721_address = lock_erc721.functions.clonesErc721(mainnet_hash, erc721_address_mainnet).call() + if erc721_address == '0x0000000000000000000000000000000000000000': + raise ValueError('No such token') + with open(self.config.proxy_root + '/artifacts/contracts/schain/tokens/ERC721OnChain.sol/ERC721OnChain.json') as erc721_on_chain_file: + erc721_on_chain_json = json.load(erc721_on_chain_file) + return self.web3_schain.eth.contract(address=erc721_address, abi=erc721_on_chain_json['abi']) + + def get_erc1155_on_schain(self, schain_name, erc1155_address_mainnet): + lock_erc1155 = self._get_contract_on_schain('token_manager_erc1155') + mainnet_hash = Web3.solidityKeccak(['string'], ["Mainnet"]) + erc1155_address = lock_erc1155.functions.clonesErc1155(mainnet_hash, erc1155_address_mainnet).call() + if erc1155_address == '0x0000000000000000000000000000000000000000': + raise ValueError('No such token') + with open(self.config.proxy_root + '/artifacts/contracts/schain/tokens/ERC1155OnChain.sol/ERC1155OnChain.json') as erc1155_on_chain_file: + erc1155_on_chain_json = json.load(erc1155_on_chain_file) + return self.web3_schain.eth.contract(address=erc1155_address, abi=erc1155_on_chain_json['abi']) + + def get_erc20_on_mainnet(self, index): + lock_erc20 = self._get_contract_on_mainnet('deposit_box_erc20') + erc20_address = lock_erc20.functions.erc20Tokens(index).call() + if erc20_address == '0x0000000000000000000000000000000000000000': + raise ValueError('No such token') + with open(self.config.test_resource_dir + '/ERC20MintableDetailed.json') as erc20_file: + erc20_on_mainnet_json = json.load(erc20_file) + return self.web3_schain.eth.contract(address=erc20_address, abi=erc20_on_mainnet_json['abi']) + + def get_erc721_on_mainnet(self, index): + lock_erc721 = self._get_contract_on_mainnet('deposit_box_erc721') + erc721_address = lock_erc721.functions.erc721Tokens(index).call() + if erc721_address == '0x0000000000000000000000000000000000000000': + raise ValueError('No such token') + with open(self.config.test_resource_dir + '/ERC721FullMetadataMintable.json') as erc721_file: + erc721_on_mainnet_json = json.load(erc721_file) + return self.web3_schain.eth.contract(address=erc721_address, abi=erc721_on_mainnet_json['abi']) + + def get_erc1155_on_mainnet(self, index): + lock_erc1155 = self._get_contract_on_mainnet('deposit_box_erc1155') + erc1155_address = lock_erc1155.functions.erc1155Tokens(index).call() + if erc1155_address == '0x0000000000000000000000000000000000000000': + raise ValueError('No such token') + with open(self.config.test_resource_dir + '/ERC1155BurnableMintable.json') as erc1155_file: + erc1155_on_mainnet_json = json.load(erc1155_file) + return self.web3_schain.eth.contract(address=erc1155_address, abi=erc1155_on_mainnet_json['abi']) + + # private + + def _get_contact(self, web3, json_filename, name): + with open(json_filename) as abi_file: + abis = json.load(abi_file) + contract = web3.eth.contract( + address=abis[name + '_address'], + abi=abis[name + '_abi'] + ) + return contract + + def _get_contract_on_schain(self, name): + return self._get_contact(self.web3_schain, self.config.abi_schain, name) + + def _get_contract_on_mainnet(self, name): + return self._get_contact(self.web3_mainnet, self.config.abi_mainnet, name) + + @staticmethod + def _deploy_contract_from_json(web3, json_filename, constructor_arguments, private_key): + with open(json_filename) as json_file: + address = BlockChain.key_to_address(private_key) + + json_contract = json.load(json_file) + abi = json_contract['abi'] + bytecode = json_contract['bytecode'] + contract = web3.eth.contract(abi=abi, bytecode=bytecode) + + nonce = web3.eth.getTransactionCount(address) + + deploy_txn = contract.constructor(*constructor_arguments).buildTransaction({ + 'gas': 4712388, + 'gasPrice': web3.toWei('1', 'gwei'), + 'nonce': nonce, + }) + signed_txn = web3.eth.account.signTransaction(deploy_txn, private_key=private_key) + transaction_hash = web3.eth.sendRawTransaction(signed_txn.rawTransaction) + # + receipt = BlockChain.await_receipt(web3, transaction_hash) + # + contract = web3.eth.contract(address=receipt.contractAddress, abi=abi) + return contract + + @staticmethod + def await_receipt(web3, tx, retries=10, timeout=5): + for _ in range(0, retries): + receipt = BlockChain.get_receipt(web3, tx) + if (receipt != None): + return receipt + sleep( timeout ) + return None + + @staticmethod + def get_receipt(web3, tx): + return web3.eth.getTransactionReceipt(tx) + + def _deploy_contract_to_mainnet(self, json_filename, constructor_arguments, private_key): + return self._deploy_contract_from_json(self.web3_mainnet, json_filename, constructor_arguments, private_key) + diff --git a/test/tools/config.py b/test/tools/config.py new file mode 100644 index 00000000..80f27bdb --- /dev/null +++ b/test/tools/config.py @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +class Config: + src_root = '.' + agent_src = 'src' + proxy_root = 'IMA/proxy' + test_root = 'test' + test_working_dir = 'working' + test_resource_dir = 'resources' + network_for_mainnet = 'mainnet' + network_for_schain = 'schain' + mainnet_key='0x81c26527399eb89edc444159889bcf42f8f425522ec2e6e65b1468ad84312524' + mainnet_rpc_url='http://127.0.0.1:8545' + schain_key = '0x9a862326bb0e585db6dedc8cba43877124b0acc6556764b246f3298a71dbc241' + schain_rpc_url = 'http://127.0.0.1:8545' + schain_name = 'd2' + schain_name_2 = 'd3' + abi_mainnet = 'proxyMainnet.json' + abi_schain = 'proxySchain_' + abi_schain_2 = 'proxySchain_' + user_key = '' + + def __init__(self, src_root, config_json): + self.agent_src = src_root + '/' + self.agent_src + self.proxy_root = src_root + '/' + self.proxy_root + self.agent_root = src_root + '/src' + self.test_root = src_root + '/' + self.test_root + self.test_working_dir = self.test_root + '/' + self.test_working_dir + self.test_resource_dir = self.test_root + '/' + self.test_resource_dir + + if 'NETWORK_FOR_ETHEREUM' in config_json: + self.network_for_mainnet = config_json['NETWORK_FOR_ETHEREUM'] + if 'NETWORK_FOR_SCHAIN' in config_json: + self.network_for_schain = config_json['NETWORK_FOR_SCHAIN'] + self.mainnet_key = config_json['PRIVATE_KEY_FOR_ETHEREUM'] + if 'URL_W3_ETHEREUM' in config_json: + self.mainnet_rpc_url = config_json['URL_W3_ETHEREUM'] + self.schain_key = config_json['PRIVATE_KEY_FOR_SCHAIN'] + if 'URL_W3_S_CHAIN' in config_json: + self.schain_rpc_url = config_json['URL_W3_S_CHAIN'] + if 'CHAIN_NAME_SCHAIN' in config_json: + self.schain_name = config_json['CHAIN_NAME_SCHAIN'] + if 'user_key' in config_json: + self.user_key = config_json['user_key'] + + self.abi_mainnet = self.proxy_root + '/data/' + self.abi_mainnet + self.abi_schain = self.proxy_root + '/data/' + self.abi_schain + self.schain_name + '.json' + self.abi_schain_2 = self.proxy_root + '/data/' + self.abi_schain_2 + self.schain_name_2 + '.json' + + diff --git a/test/tools/config_generator.py b/test/tools/config_generator.py new file mode 100644 index 00000000..3cbc955b --- /dev/null +++ b/test/tools/config_generator.py @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +import json +from tools.config import Config + +def config_generator(src_root, json_filename): + def _internal_config_generator(current_preconfig): + if type(current_preconfig) is not dict: + raise TypeError('Config should be a dictionary') + + is_config = True + for key, value in current_preconfig.items(): + if type(value) is list: + is_config = False + for current_value in value: + if type(current_value) is dict: + preconfig_copy = current_preconfig.copy() + preconfig_copy.pop(key) + for config_object in _internal_config_generator({**preconfig_copy, **current_value}): + yield config_object + else: + for config_object in _internal_config_generator({**current_preconfig, key: value}): + yield config_object + break + + if is_config: + yield Config(src_root, current_preconfig) + + with open(json_filename) as config_file: + preconfig = json.load(config_file) + for config in _internal_config_generator(preconfig): + yield config diff --git a/test/tools/environment.py b/test/tools/environment.py new file mode 100644 index 00000000..e5375d16 --- /dev/null +++ b/test/tools/environment.py @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +class Environment: + + def prepare(self): + pass diff --git a/test/tools/test_case.py b/test/tools/test_case.py new file mode 100644 index 00000000..cdf973e7 --- /dev/null +++ b/test/tools/test_case.py @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from tools.blockchain import BlockChain +from proxy.deployer import Deployer +from agent.agent import Agent +from time import time +from logging import error + +class TestCase: + name = None + deployer = None + agent = None + passed = False + blockchain = None + config = None + time_started = time() + timeout = None + timeout_of_entire_test = None + + def __init__(self, name, config, timeout=10, timeout_of_entire_test=80000): + self.name = name + self.deployer = Deployer(config) + self.agent = Agent(config) + self.blockchain = BlockChain(config) + self.config = config + self.timeout = timeout + self.timeout_of_entire_test = timeout_of_entire_test + + + def prepare(self): + self.deployer.deploy() + self.agent.register() + self.agent.start() + self._prepare() + + def execute(self): + self.time_started = time() + self._execute() + if self._timeout(): + error(f'CRITICAL INTEGRATION TEST ERROR: Test "{self.name}" will be marked failed due to timeout') + self.passed = False + + def clean_up(self): + self.agent.stop() + self._clean_up() + + def is_passed(self): + return self.passed + + def get_name(self): + return self.name + + # protected + + def _prepare(self): + pass + + def _execute(self): + pass + + def _clean_up(self): + pass + + def _mark_passed(self): + self.passed = True + + def _timeout(self): + if self.timeout is not None and time() > self.time_started + self.timeout_of_entire_test: + return True + else: + return False + return False diff --git a/test/tools/test_pool.py b/test/tools/test_pool.py new file mode 100644 index 00000000..3ea98462 --- /dev/null +++ b/test/tools/test_pool.py @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +class TestPool: + tests = [] + + def get_tests(self, config): + return [test(config) for test in self.tests] + + + def register_test(self, test): + self.tests.append(test) + + +test_pool = TestPool() \ No newline at end of file diff --git a/test/tools/test_runner.py b/test/tools/test_runner.py new file mode 100644 index 00000000..638ca0d3 --- /dev/null +++ b/test/tools/test_runner.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from logging import info, error + +from tools.config_generator import config_generator +from tools.test_pool import test_pool + +from test_cases import * +# Do not remove this unused import. It is needed for tests registration + + +class TestRunner: + src_root = None + config_filename = 'config.json' + tests = None + + def __init__(self, src_root, config_filename, tests): + self.src_root = src_root + self.config_filename = config_filename + self.tests = tests + + def run(self): + for config in config_generator(self.src_root, self.config_filename): + for test in test_pool.get_tests(config): + test_name = test.get_name() + if self.tests is not None and test_name not in self.tests: + info(f'Skip test {test_name}') + continue + else: + info(f'Execute test {test_name}') + + info(f'Preparing test {test_name}') + test.prepare() + info(f'Starting test {test_name}') + test.execute() + info(f'Cleaning up after test {test_name}') + test.clean_up() + + if test.is_passed(): + info(f'Test "{test_name}" passed') + else: + error(f'CRITICAL INTEGRATION TEST ERROR: Test "{test_name}" failed') + exit(1) + info('All tests passed') diff --git a/test/tools/utils.py b/test/tools/utils.py new file mode 100644 index 00000000..40d04a4d --- /dev/null +++ b/test/tools/utils.py @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# -*- coding: utf-8 -*- +# +# This file is part of SKALE IMA. +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE IMA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE IMA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE IMA. If not, see . + +from logging import error + +from os import system + +def execute(command: str, print_command=False): + if print_command: + print('Execute:', command) + exit_code = system(command) + if exit_code: + error(f'CRITICAL INTEGRATION TEST ERROR: Command "{command}" failed with exit code {exit_code}') + exit(1) diff --git a/test/unitTests/agentUnitTests.js b/test/unitTests/agentUnitTests.js new file mode 100644 index 00000000..60e24165 --- /dev/null +++ b/test/unitTests/agentUnitTests.js @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE IMA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file agentUnitTests.js + * @copyright SKALE Labs 2019-Present + */ + +const assert = require( "chai" ).assert; +const expect = require( "chai" ).expect; +const IMA = require( "../../src/build/imaCore.js" ); +const imaTx = require( "../../src/build/imaTx.js" ); +const imaReg = require( "../../src/build/imaRegistrationOperations.js" ); +const imaEth = require( "../../src/build/imaEthOperations.js" ); +const imaToken = require( "../../src/build/imaTokenOperations.js" ); +const transactionCustomizerMainNet = imaTx.getTransactionCustomizerForMainNet(); +const transactionCustomizerSChain = imaTx.getTransactionCustomizerForSChain(); + +const chainNameMainNet = "Mainnet"; +const chainNameSChain = "blah_blah_blah_schain_name"; // 1; + +// mockup for `ethersProviderSrc` +const ethersProviderSrc = { eth: { getBlockNumber: getBlockNumber, getBlock: getBlock } }; +function getBlockNumber( string ) { + return 2; +} +function getBlock( number ) { + return { timestamp: "1469021581" }; +} + +// mockup for `ethersProviderDst` +const ethersProviderDst = { + eth: { + sendSignedTransaction: sendSignedTransaction, + getTransactionCount: getTransactionCount + }, + utils: { hexToAscii: hexToAscii, asciiToHex: asciiToHex } +}; +function hexToAscii( string ) { + return "0"; +} +function asciiToHex( string ) { + return "0x0"; +} + +// mockup for `ethersProviderMainNet` +const ethersProviderMainNet = { + eth: { + sendSignedTransaction: sendSignedTransaction, + Contract: Contract, + getTransactionCount: getTransactionCount + }, + utils: { fromAscii: fromAscii, fromWei: fromWei, toBN: toBN, toHex: toHex, toWei: toWei } +}; +// mockup for `ethersProviderSChain` +const ethersProviderSChain = { + eth: { + sendSignedTransaction: sendSignedTransaction, + Contract: Contract, + getTransactionCount: getTransactionCount + }, + utils: { fromAscii: fromAscii, fromWei: fromWei, toBN: toBN, toHex: toHex, toWei: toWei } +}; +function sendSignedTransaction( string ) { + return true; +} +function getTransactionCount( string ) { + assert( string !== undefined ); + return 1; +} +function fromAscii( string ) { + return "0"; +} +function fromWei( stringA, stringB ) { + return "0"; +} +function toBN( string ) { + return "0"; +} +function Contract( stringA, stringB ) { + return { "methods": { "approve": approve } }; +} +function approve( stringA, stringB ) { + return { "encodeABI": encodeABI }; +} +function toHex( string ) { + return "0x9a"; +} +function toWei( stringA, stringB ) { + return 100; +} + +// mockup for `joAccountDst` +const joAccountDst = { + "address": function () { return IMA.owaspUtils.fnAddressImpl_( this ); }, + privateKey: "6270720ecca0185a979b6791bea433e9dbf23345e5b5b1b0258b1fbaf32b4390" +}; +// mockup for `joAccountSrc` +const joAccountSrc = { + "address": function () { return IMA.owaspUtils.fnAddressImpl_( this ); }, + privateKey: "6270720ecca0185a979b6791bea433e9dbf23345e5b5b1b0258b1fbaf32b4390" +}; +// mockup for `joMainNetAccount` +const joMainNetAccount = { + "address": function () { return IMA.owaspUtils.fnAddressImpl_( this ); }, + privateKey: "6270720ecca0185a979b6791bea433e9dbf23345e5b5b1b0258b1fbaf32b4390" +}; +// mockup for `joDepositBox` +const joDepositBox = { + "methods": { + deposit: deposit, + depositERC20: depositERC20, + rawDepositERC20: rawDepositERC20 + }, + options: { "address": "0xd34e38f830736DB41CC6E10aA37A3C851A7a2B82" } +}; +function deposit( stringA, stringB, stringC ) { + return { "encodeABI": encodeABI }; +} +function depositERC20( stringA, stringB, stringC, stringD ) { + return { "encodeABI": encodeABI }; +} +function rawDepositERC20( stringA, stringB, stringC, stringD, stringE ) { + return { "encodeABI": encodeABI }; +} + +// mockup for `joLockAndDataMainNet` +const joLockAndDataMainNet = { + "methods": { + hasSchain: hasSchain, + addSchain: addSchain, + getMyEth: getMyEth, + approveTransfers: approveTransfers + }, + options: { "address": "0xd34e38f830736DB41CC6E10aA37A3C851A7a2B82" } +}; +function hasSchain( string ) { + return { "call": call }; +} +function approveTransfers( string ) { + return { "call": call }; +} +function addSchain( string, arr ) { + return { "encodeABI": encodeABI }; +} +function getMyEth() { + return { "encodeABI": encodeABI }; +} + +// mockup for `joLockAndDataSChain` +const joLockAndDataSChain = { + "methods": { hasDepositBox: hasDepositBox, addDepositBox: addDepositBox }, + options: { "address": "0xd34e38f830736DB41CC6E10aA37A3C851A7a2B82" } +}; +function hasDepositBox() { + return { "call": call }; +} +function addDepositBox( string ) { + return { "encodeABI": encodeABI }; +} +function encodeABI() { + return "0x0"; +} + +// mockup for `joMessageProxyDst` +const joMessageProxyDst = { + "methods": { + getIncomingMessagesCounter: getIncomingMessagesCounter, + postIncomingMessages: postIncomingMessages + }, + options: { "address": "0xd34e38f830736DB41CC6E10aA37A3C851A7a2B82" } +}; +function getIncomingMessagesCounter( string ) { + return { "call": callNum }; +} +function postIncomingMessages( string, obj ) { + return { "encodeABI": encodeABI }; +} + +// mockup for `joMessageProxySrc` +const joMessageProxySrc = { + "methods": { getOutgoingMessagesCounter: getOutgoingMessagesCounter }, + getPastEvents: getPastEvents +}; + +function getOutgoingMessagesCounter( string ) { + return { "call": callNum }; +} +function callNum() { + return 3; +} + +// mockup for `joTokenManager` +const joTokenManager = { + "methods": { + exitToMain: exitToMain, + exitToMainERC20: exitToMainERC20, + rawExitToMainERC20: rawExitToMainERC20 + }, + options: { "address": "0xd34e38f830736DB41CC6E10aA37A3C851A7a2B82" }, + getPastEvents: getPastEvents +}; +function exitToMain( string ) { + return { "encodeABI": encodeABI }; +} +function getPastEvents( string, obj ) { + return "events stub"; +} +function rawExitToMainERC20( string, obj ) { + return { "encodeABI": encodeABI }; +} +function exitToMainERC20( string, obj ) { + return { "encodeABI": encodeABI }; +} + +describe( "tests for `IMA Core` 1", function() { + + it( "should invoke `verboseGet`", async function() { + expect( log.verboseGet() ).to.equal( "3" ); + } ); + + it( "should invoke `verboseSet`", async function() { + log.verboseSet( "0" ); + expect( log.verboseGet() ).to.equal( "0" ); + } ); + + it( "should invoke `verboseParse`", async function() { + // return 5 by default + expect( log.verboseParse() ).to.equal( 5 ); + // return 6 when `info` in parameters + expect( log.verboseParse( "info" ) ).to.equal( "6" ); + } ); + + it( "should invoke `ensureStartsWith0x`", async function() { + const string = "123456789"; + expect( IMA.owaspUtils.ensureStartsWith0x( string ) ).to.be.equal( "0" + "x" + string ); + } ); + + it( "should invoke `removeStarting0x`", async function() { + const string = "0x123456789"; + expect( IMA.owaspUtils.removeStarting0x( string ) ).to.be.equal( string.substr( 2 ) ); + // not string + expect( IMA.owaspUtils.removeStarting0x( 321 ) ).to.be.equal( 321 ); + // short string less than 2 + expect( IMA.owaspUtils.removeStarting0x( "1" ) ).to.be.equal( "1" ); + } ); + +} ); + +describe( "tests for `IMA Core` 2", function() { + + it( "should return `false` invoke `checkIsRegisteredSChainInDepositBoxes`", async function() { + let joLinker; // for `false` output + // eslint-disable-next-line no-unused-expressions + expect( await imaReg.checkIsRegisteredSChainInDepositBoxes( + ethersProviderMainNet, joLinker, joMainNetAccount, chainNameSChain + ) ).to.be.false; + } ); + + it( "should return `true` invoke `checkIsRegisteredSChainInDepositBoxes`", async function() { + // eslint-disable-next-line no-unused-expressions + expect( await imaReg.checkIsRegisteredSChainInDepositBoxes( + ethersProviderMainNet, joLinker, joMainNetAccount, chainNameSChain + ) ).to.be.true; + } ); + + it( "should return `false` invoke `registerSChainInDepositBoxes`", async function() { + let joLinker; // for `false` output + let joTokenManagerETH; // only s-chain + let joTokenManagerERC20; // only s-chain + let joTokenManagerERC721; // only s-chain + let joTokenManagerERC1155; // only s-chain + let joTokenManagerERC721WithMetadata; // only s-chain + let joCommunityLocker; // only s-chain + let joTokenManagerLinker; + // eslint-disable-next-line no-unused-expressions + expect( await imaReg.registerSChainInDepositBoxes( + ethersProviderMainNet, + joLinker, + joMainNetAccount, + joTokenManagerETH, // only s-chain + joTokenManagerERC20, // only s-chain + joTokenManagerERC721, // only s-chain + joTokenManagerERC1155, // only s-chain + joTokenManagerERC721WithMetadata, // only s-chain + joCommunityLocker, // only s-chain + joTokenManagerLinker, + chainNameSChain, + chainNameMainNet, + transactionCustomizerMainNet, + 1, + 1000 + ) ).to.be.false; + } ); + + it( "should return `false` invoke `doEthPaymentFromMainNet`", async function() { + let joAccountSrc, wei_how_much; // for `false` output + // eslint-disable-next-line no-unused-expressions + expect( await imaEth.doEthPaymentFromMainNet( + ethersProviderMainNet, + joAccountSrc, + joAccountDst, + joDepositBox, + chainNameSChain, + wei_how_much, // how much WEI money to send + transactionCustomizerMainNet + ) ).to.be.false; + } ); + + it( "should return `true` invoke `doEthPaymentFromMainNet`", async function() { + let wei_how_much; + // eslint-disable-next-line no-unused-expressions + expect( await imaEth.doEthPaymentFromMainNet( + ethersProviderMainNet, + joAccountSrc, + joAccountDst, + joDepositBox, + chainNameSChain, + wei_how_much, // how much WEI money to send + transactionCustomizerMainNet + ) ).to.be.true; + } ); + + it( "should return `false` invoke `doEthPaymentFromSChain`", async function() { + let joAccountSrc, wei_how_much; // for `false` output + // eslint-disable-next-line no-unused-expressions + expect( await imaEth.doEthPaymentFromSChain( + ethersProviderSChain, + joAccountSrc, + joAccountDst, + joTokenManager, + joLockAndDataSChain, + wei_how_much, // how much WEI money to send + transactionCustomizerSChain + ) ).to.be.false; + } ); + + it( "should return `true` invoke `doEthPaymentFromSChain`", async function() { + let wei_how_much; + // eslint-disable-next-line no-unused-expressions + expect( await imaEth.doEthPaymentFromSChain( + ethersProviderSChain, + joAccountSrc, + joAccountDst, + joTokenManager, + joLockAndDataSChain, + wei_how_much, // how much WEI money to send + transactionCustomizerSChain + ) ).to.be.true; + } ); + + it( "should return `false` invoke `receiveEthPaymentFromSchainOnMainNet`", async function() { + let joMainNetAccount; // for `false` output + // eslint-disable-next-line no-unused-expressions + expect( await imaEth.receiveEthPaymentFromSchainOnMainNet( + ethersProviderMainNet, + joMainNetAccount, + joLockAndDataMainNet, + transactionCustomizerMainNet + ) ).to.be.false; + } ); + + it( "should return `true` invoke `receiveEthPaymentFromSchainOnMainNet`", async function() { + // eslint-disable-next-line no-unused-expressions + expect( await imaEth.receiveEthPaymentFromSchainOnMainNet( + ethersProviderMainNet, + joMainNetAccount, + joLockAndDataMainNet, + transactionCustomizerMainNet + ) ).to.be.true; + } ); + + it( "should return `null` invoke `viewEthPaymentFromSchainOnMainNet`", async function() { + let joMainNetAccount; // for `false` output + // eslint-disable-next-line no-unused-expressions + expect( await imaEth.viewEthPaymentFromSchainOnMainNet( + ethersProviderMainNet, + joMainNetAccount, + joLockAndDataMainNet + ) ).to.be.null; + } ); + + it( "should return `true` invoke `viewEthPaymentFromSchainOnMainNet`", async function() { + // eslint-disable-next-line no-unused-expressions + expect( await imaEth.viewEthPaymentFromSchainOnMainNet( + ethersProviderMainNet, + joMainNetAccount, + joLockAndDataMainNet + ) ).to.be.true; + } ); + +} ); + +describe( "tests for `IMA Core` 3", function() { + + it( "should return `false` invoke `doErc20PaymentFromMainNet`", async function() { + let nAmountOfToken; + let strCoinNameErc20MainNet; + let erc20PrivateTestnetJsonMainNet; + let strCoinNameErc20SChain; + let erc20PrivateTestnetJsonSChain; + // eslint-disable-next-line no-unused-expressions + expect( await imaToken.doErc20PaymentFromMainNet( + ethersProviderMainNet, + ethersProviderSChain, + joAccountSrc, + joAccountDst, + joDepositBox, + chainNameSChain, + nAmountOfToken, // how much ERC20 tokens to send + joTokenManager, // only s-chain + strCoinNameErc20MainNet, + erc20PrivateTestnetJsonMainNet, + strCoinNameErc20SChain, + erc20PrivateTestnetJsonSChain, + transactionCustomizerMainNet + ) ).to.be.false; + } ); + + it( "should return `false` invoke `doErc20PaymentFromSChain`", async function() { + let nAmountOfToken; + let strCoinNameErc20MainNet; + let joErc20MainNet; + let strCoinNameErc20SChain; + let joErc20SChain; + // eslint-disable-next-line no-unused-expressions + expect( await imaToken.doErc20PaymentFromSChain( + ethersProviderMainNet, + ethersProviderSChain, + joAccountSrc, + joAccountDst, + joTokenManager, // only s-chain + joLockAndDataSChain, + joDepositBox, // only main net + nAmountOfToken, // how much ERC20 tokens to send + strCoinNameErc20MainNet, + joErc20MainNet, + strCoinNameErc20SChain, + joErc20SChain, + transactionCustomizerSChain + ) ).to.be.false; + } ); + + it( "should return `false` invoke `doTransfer`", async function() { + let joMessageProxySrc; // for `false` output + const chainNameSrc = "test"; + const chainNameDst = "test"; + const nTransactionsCountInBlock = 4; + const nTransferSteps = 0; + const nMaxTransactionsCount = 0; + const nBlockAwaitDepth = 0; + const nBlockAge = 0; + const joRuntimeOpts = { + isInsideWorker: false, + idxChainKnownForS2S: 0, + cntChainsKnownForS2S: 0 + }; + // eslint-disable-next-line no-unused-expressions + expect( await IMA.doTransfer( + "M2S", + joRuntimeOpts, + ethersProviderSrc, + joMessageProxySrc, + joAccountSrc, + ethersProviderDst, + joMessageProxyDst, + joAccountDst, + chainNameSrc, + chainNameDst, + -4, + -4, + null, // joDepositBox - for logs validation on mainnet + null, // joTokenManager - for logs validation on s-chain + nTransactionsCountInBlock, + nTransferSteps, + nMaxTransactionsCount, + nBlockAwaitDepth, + nBlockAge, + null, + transactionCustomizerMainNet, // or transactionCustomizerSChain + null + ) ).to.be.false; + } ); + + it( "should return `true` invoke `doTransfer`", async function() { + const chainNameSrc = "test"; + const chainNameDst = "test"; + const nTransactionsCountInBlock = 4; + const nTransferSteps = 0; + const nMaxTransactionsCount = 0; + const nBlockAwaitDepth = 0; + const nBlockAge = 0; + const joRuntimeOpts = { + isInsideWorker: false, + idxChainKnownForS2S: 0, + cntChainsKnownForS2S: 0 + }; + // eslint-disable-next-line no-unused-expressions + expect( await IMA.doTransfer( + "M2S", + joRuntimeOpts, + ethersProviderSrc, + joMessageProxySrc, + joAccountSrc, + ethersProviderDst, + joMessageProxyDst, + joAccountDst, + chainNameSrc, + chainNameDst, + -4, + -4, + null, // joDepositBox - for logs validation on mainnet + null, // joTokenManager - for logs validation on s-chain + nTransactionsCountInBlock, + nTransferSteps, + nMaxTransactionsCount, + nBlockAwaitDepth, + nBlockAge, + null, + transactionCustomizerMainNet, // or transactionCustomizerSChain + null + ) ).to.be.true; + } ); +} ); diff --git a/test/unitTests/mocha.opts b/test/unitTests/mocha.opts new file mode 100644 index 00000000..4893d9d6 --- /dev/null +++ b/test/unitTests/mocha.opts @@ -0,0 +1,2 @@ +--exit +--timeout 50000 \ No newline at end of file diff --git a/test/unitTests/testSocketServer.mjs b/test/unitTests/testSocketServer.mjs new file mode 100644 index 00000000..27bd12b1 --- /dev/null +++ b/test/unitTests/testSocketServer.mjs @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE COOL SOCKET + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file testSocketServer.mjs + * @copyright SKALE Labs 2019-Present + */ + +import { SocketServer } from "../../src/build/socketServer.js"; + +export class TestSocketServer extends SocketServer { + constructor( acceptor ) { + super( acceptor ); + const self = this; + self.mapApiHandlers.echo = function( joMessage, joAnswer, eventData, socket ) { + console.log( "SERVER <<<", JSON.stringify( joMessage ) ); + joAnswer.message = joMessage.message ? joMessage.message : ""; + console.log( "SERVER >>>", JSON.stringify( joAnswer ) ); + return joAnswer; + }; + } + dispose() { + this.isDisposing = true; + super.dispose(); + } +}; diff --git a/test/unitTests/testSocketSignalingServer.mjs b/test/unitTests/testSocketSignalingServer.mjs new file mode 100644 index 00000000..b59a6849 --- /dev/null +++ b/test/unitTests/testSocketSignalingServer.mjs @@ -0,0 +1,880 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE COOL SOCKET + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file testSocketSignalingServer.mjs + * @copyright SKALE Labs 2019-Present + */ + +import * as fs from "fs"; + +import * as httpsModuleLoaded from "https"; +import * as wsModuleLoaded from "ws"; +import * as webRtcModuleLoaded from "wrtc"; + +import * as networkLayer from "../../src/build/socket.js"; +import { settings } from "../../src/build/socketSettings.js"; +import { UniversalDispatcherEvent, EventDispatcher } from "../../src/build/eventDispatcher.js"; +import * as utils from "../../src/build/socketUtils.js"; + +const httpsModule = httpsModuleLoaded; // .default; +const wsModule = wsModuleLoaded; // .default; +const webRtcModule = webRtcModuleLoaded; // .default; + +networkLayer.setHttpsModule( httpsModule ); +networkLayer.setWsModule( wsModule ); +networkLayer.setWebRtcModule( webRtcModule ); + +console.log( "Test signaling server application..." ); +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +class SignalingClient extends EventDispatcher { + constructor( idRtcParticipant, strRole, signalingSpace, socket ) { + super(); + this.isDisposed = false; + this.idRtcParticipant = "" + + ( ( idRtcParticipant && + typeof idRtcParticipant == "string" && + idRtcParticipant.length > 0 ) + ? idRtcParticipant : "" ); + this.isCreator = ( strRole == "creator" ) ? true : false; + this.isJoiner = ( strRole == "joiner" ) ? true : false; + this.signalingSpace = signalingSpace; + this.socket = socket; + socket.signalingClient = this; + if( this.isCreator ) + this.signalingSpace.idSomebodyCreator = "" + this.idRtcParticipant; + this.signalingSpace.mapClients[this.idRtcParticipant] = this; + this.idSpace = "" + this.signalingSpace.idSpace; + this.idCategory = "" + this.signalingSpace.signalingCategory.idCategory; + this.isFetchingOffer = false; + this.timerFetchingOffer = null; + this.fetchingOfferStepNumber = 0; + if( settings.logging.net.signaling.objectLifetime ) { + console.log( + "New signaling client \"" + this.idRtcParticipant + "\" in signaling space \"" + + this.idSpace + "\" in signaling category \"" + this.idCategory + + "\" using socket " + this.socket.strSavedRemoteAddress ); + } + this.signalingSpace.dispatchEvent( + new UniversalDispatcherEvent( + "clientAdded", + { "detail": { "signalingClient": this } } ) ); + } + dispose() { + if( this.isDisposed ) + return; + this.isDisposing = true; + if( settings.logging.net.signaling.objectLifetime ) { + console.log( + "Disposing signaling client \"" + this.idRtcParticipant + + "\" in signaling space \"" + this.idSpace + + "\" in signaling category \"" + this.idCategory + "\"" ); + } + this.disconnect(); + if( this.idRtcParticipant ) { + if( this.signalingSpace ) { + this.signalingSpace.dispatchEvent( + new UniversalDispatcherEvent( + "clientRemoved", + { "detail": { "signalingClient": this } } ) ); + delete this.signalingSpace.mapClients[this.idRtcParticipant]; + } + this.idRtcParticipant = null; + } + if( this.isCreator ) { + if( this.signalingSpace ) + this.signalingSpace.idSomebodyCreator = ""; + } + this.isCreator = false; + this.isJoiner = false; + this.idSpace = null; + this.idCategory = null; + this.signalingSpace.autoDispose(); + this.signalingSpace = null; + super.dispose(); + } + disconnect() { + if( this.isDisposed || ( !this.socket ) ) + return; + this.offerDiscoveryStop(); + let bPass = false, anyError = null; + try { + this.socket.disconnect(); + bPass = true; + } catch ( err ) { + anyError = err; + } + if( ! bPass ) { + if( settings.logging.net.signaling.error ) { + console.warn( + "Signaling client \"" + this.idRtcParticipant + "\" in signaling space \"" + + his.idSpace + "\" in signaling category \"" + this.idCategory + + "\" - web socket signaling pipe termination error", anyError ); + } + } + + this.socket.signalingClient = null; + this.socket = null; + if( settings.logging.net.signaling.disconnect ) { + console.warn( + "Disconnected/force signaling client \"" + this.idRtcParticipant + + "\" in signaling space \"" + this.idSpace + "\" in signaling category \"" + + this.idCategory + "\"" ); + } + } + onPipeClose( socket ) { + if( this.isDisposed ) + return; + if( settings.logging.net.signaling.disconnect ) { + console.warn( + "Disconnected/pipe signaling client \"" + this.idRtcParticipant + + "\" in signaling space \"" + this.idSpace + "\" in signaling category \"" + + this.idCategory + "\"" ); + } + this.offerDiscoveryStop(); + this.dispose(); + } + onPipeError( socket ) { + if( this.isDisposed ) + return; + if( settings.logging.net.signaling.error ) { + console.warn( + "Disconnected/error signaling client \"" + this.idRtcParticipant + + "\" in signaling space \"" + this.idSpace + "\" in signaling category \"" + + this.idCategory + "\"" ); + } + this.offerDiscoveryStop(); + this.dispose(); + } + offerDiscoveryStop() { + if( ! this.isFetchingOffer ) + return; + if( this.timerFetchingOffer ) { + clearInterval( this.timerFetchingOffer ); + this.timerFetchingOffer = null; + } + this.isFetchingOffer = false; + this.fetchingOfferStepNumber = 0; + } + offerDiscoveryStart( joMessage ) { + if( this.isFetchingOffer ) + return; + this.isFetchingOffer = true; + this.offerDiscoveryStep( joMessage ); + } + offerDiscoveryStep( joMessage ) { + if( ! this.isFetchingOffer ) + return; + let joAnswer = null; + ++ this.fetchingOfferStepNumber; + try { + const signalingSpace = this.signalingSpace; + const joOfferInfo = signalingSpace.fetchPublishedOffer(); + if( ! joOfferInfo ) { + if( settings.logging.net.signaling.offerDiscoveryStepFail ) { + console.warn( + "Signaling client socket \"" + this.socket.strSavedRemoteAddress + + "\" did not found offer at step", this.fetchingOfferStepNumber, "of", + settings.net.rtc.offerDiscovery.stepCount ); + } + if( this.fetchingOfferStepNumber >= settings.net.rtc.offerDiscovery.stepCount ) { + this.offerDiscoveryStop(); + throw new Error( "no offer found" ); + } + if( ! this.timerFetchingOffer ) { + const self = this; + this.timerFetchingOffer = setInterval( function() { + self.offerDiscoveryStep( joMessage ); + }, settings.net.rtc.offerDiscovery.periodMilliseconds ); + } + return; + } + if( settings.logging.net.signaling.impersonate ) { + console.log( + "Signaling client socket \"" + this.socket.strSavedRemoteAddress + + "\" impersonated as \"" + this.idRtcParticipant + + "\" in signaling space \"" + signalingSpace.idSpace + + "\" did fetched published offer:", joOfferInfo ); + } + joAnswer = utils.prepareAnswerJSON( joMessage ); // successful answer + joAnswer.offer = joOfferInfo.offer; + joAnswer.idOffer = 0 + joOfferInfo.idOffer; + joAnswer.idSomebodyCreator = joOfferInfo.idSomebodyCreator; // server holder + } catch ( err ) { + if( settings.logging.net.signaling.error ) + console.warn( "Server method", joMessage.method, "RPC exception:", err ); + joAnswer = utils.prepareAnswerJSON( joMessage ); + joAnswer.error = "" + err.toString(); + } + if( typeof joAnswer.error == "string" && joAnswer.error.length > 0 ) { + if( settings.logging.net.signaling.error ) { + console.warn( + "Signaling client socket \"" + this.socket.strSavedRemoteAddress + + "\" error answer", joAnswer ); + } + } else if( settings.logging.net.signaling.message ) { + console.log( + "Signaling client socket \"" + this.socket.strSavedRemoteAddress + + " answer", joAnswer ); + } + this.socket.send( joAnswer, true ); // isFlush=true always in signaling server + this.offerDiscoveryStop(); + } +}; + +class SignalingSpace extends EventDispatcher { + constructor( idSpace, signalingCategory ) { + super(); + this.isDisposed = false; + this.idSpace = "" + + ( ( idSpace && typeof idSpace == "string" && idSpace.length > 0 ) + ? idSpace : "" ); + this.idSomebodyCreator = ""; + this.arrPublishedOffers = []; + this.signalingCategory = signalingCategory; + this.mapClients = {}; + this.signalingCategory.mapSpaces[this.idSpace] = this; + this.idCategory = "" + this.signalingCategory.idCategory; + if( settings.logging.net.signaling.objectLifetime ) { + console.log( + "New signaling space \"" + this.idSpace + "\" in signaling category \"" + + this.idCategory + "\"" ); + } + this.signalingCategory.dispatchEvent( + new UniversalDispatcherEvent( + "spaceAdded", + { "detail": { "signalingSpace": this } } ) ); + } + dispose() { + if( this.isDisposed ) + return; + this.isDisposing = true; + if( settings.logging.net.signaling.objectLifetime ) { + console.log( + "Disposing signaling space \"" + this.idSpace + "\" in signaling category \"" + + this.idCategory + "\"" ); + } + for( const [ /*idRtcParticipant*/, signalingClient ] of Object.entries( this.mapClients ) ) + signalingClient.dispose(); + if( this.idSpace ) { + if( this.signalingCategory ) { + this.signalingCategory.dispatchEvent( + new UniversalDispatcherEvent( + "spaceRemoved", + { "detail": { "signalingSpace": this } } ) ); + delete this.signalingCategory.mapSpaces[this.idSpace]; + } + this.idSpace = null; + } + this.idCategory = null; + this.signalingCategory.autoDispose(); + this.signalingCategory = null; + this.arrPublishedOffers = []; + super.dispose(); + } + autoDispose() { + if( this.isDisposed ) + return; + if( this.allSomebodyIDs().length > 0 ) + return; + if( settings.logging.net.signaling.objectLifetime ) { + console.log( + "Auto-dispose signaling space \"" + this.idSpace + "\" in signaling category \"" + + this.idCategory + "\"" ); + } + this.dispose(); + } + allSomebodyIDs() { + if( this.isDisposed ) + return []; + return Object.keys( this.mapClients ); + } + clientGet( idRtcParticipant ) { + if( this.isDisposed ) + return null; + const signalingClient = this.mapClients[idRtcParticipant]; + return signalingClient ? signalingClient : null; + } + clientRemove( idRtcParticipant ) { + if( this.isDisposed ) + return false; + idRtcParticipant = "" + ( idRtcParticipant ? idRtcParticipant.toString() : "" ); + if( idRtcParticipant in this.mapClients ) { + const signalingClient = this.mapClients[idRtcParticipant]; + signalingClient.dispose(); + this.autoDispose(); + return true; + } + return false; + } + fetchPublishedOffer() { + if( this.isDisposed || this.isDisposing ) { + if( settings.logging.net.signaling.offerDiscoveryStepFail ) + console.warn( "Attempt to fetch offer in destroyed signaling space" ); + return null; + } + if( this.idSomebodyCreator == undefined || + this.idSomebodyCreator == null || + this.idSomebodyCreator == "" + ) { + if( settings.logging.net.signaling.offerDiscoveryStepFail ) + console.warn( "Attempt to fetch offer in malformed signaling space" ); + return null; + } + if( this.arrPublishedOffers.length == 0 ) { + if( settings.logging.net.signaling.offerDiscoveryStepFail ) + console.warn( "Attempt to fetch offer in signaling space with no offers" ); + return null; + } + const joOfferInfo = this.arrPublishedOffers[0]; + this.arrPublishedOffers.splice( 0, 1 ); + joOfferInfo.idSomebodyCreator = "" + this.idSomebodyCreator; + return joOfferInfo; + } +}; + +class SignalingCategory extends EventDispatcher { + constructor( idCategory, signalingManager ) { + super(); + this.isDisposed = false; + this.idCategory = "" + + ( ( idCategory && typeof idCategory == "string" && idCategory.length > 0 ) + ? idCategory : "" ); + this.signalingManager = signalingManager; + this.mapSpaces = {}; + this.signalingManager.mapCategories[this.idCategory] = this; + if( settings.logging.net.signaling.objectLifetime ) + console.log( "New signaling category \"" + this.idCategory + "\"" ); + this.signalingManager.dispatchEvent( + new UniversalDispatcherEvent( + "categoryAdded", + { "detail": { "signalingCategory": this } } ) ); + } + dispose() { + if( this.isDisposed ) + return; + this.isDisposing = true; + if( settings.logging.net.signaling.objectLifetime ) + console.log( "Disposing signaling category \"" + this.idCategory + "\"" ); + for( const [ /*idSpace*/, signalingSpace ] of Object.entries( this.mapSpaces ) ) + signalingSpace.dispose(); + if( this.signalingManager ) { + delete this.signalingManager.mapCategories[this.idCategory]; + this.signalingManager.dispatchEvent( + new UniversalDispatcherEvent( + "categoryRemoved", + { "detail": { "signalingCategory": this } } ) ); + this.signalingManager = null; + } + this.mapSpaces = {}; + this.idCategory = null; + super.dispose(); + } + autoDispose() { + if( this.isDisposed ) + return; + if( this.allCSpaceIDs().length > 0 ) + return; + if( settings.logging.net.signaling.objectLifetime ) + console.log( "Auto-dispose signaling category \"" + this.idCategory + "\"" ); + this.dispose(); + } + allCSpaceIDs() { + if( this.isDisposed ) + return []; + return Object.keys( this.mapSpaces ); + } + spaceGet( idSpace, isAutoAlloc ) { + if( this.isDisposed ) + return null; + try { + idSpace = "" + ( idSpace ? idSpace.toString() : settings.rtcSpace.defaultSpaceName ); + isAutoAlloc = + ( isAutoAlloc == null || isAutoAlloc == undefined ) ? true : ( !!isAutoAlloc ); + let signalingSpace = null; + if( idSpace in this.mapSpaces ) + signalingSpace = this.mapSpaces[idSpace]; + else if( isAutoAlloc ) { + this.mapSpaces["" + idSpace] = + signalingSpace = + new SignalingSpace( "" + idSpace, this ); + } + return signalingSpace; + } catch ( err ) { + if( settings.logging.net.signaling.error ) + console.warn( "Signaling space retrieval error:", err ); + return null; + } + } + spaceRemove( idSpace ) { + if( this.isDisposed ) + return false; + idSpace = "" + ( idSpace ? idSpace.toString() : idSpace.rtcSpace.defaultSpaceName ); + if( idSpace in this.mapSpaces ) { + const signalingSpace = this.mapSpaces[idSpace]; + signalingSpace.dispose(); + this.autoDispose(); + return true; + } + return false; + } + fetchPublishedOffer() { + if( this.isDisposed ) + return null; + for( const [ /*idSpace*/, signalingSpace ] of Object.entries( this.mapSpaces ) ) { + const joOfferInfo = signalingSpace.fetchPublishedOffer(); + if( joOfferInfo ) + return joOfferInfo; + } + return null; + } +}; + +class SignalingManager extends EventDispatcher { + constructor() { + super(); + this.isDisposed = false; + this.mapCategories = {}; + if( settings.logging.net.signaling.objectLifetime ) + console.log( "New signaling manager" ); + } + dispose() { + if( this.isDisposed ) + return; + this.isDisposing = true; + if( settings.logging.net.signaling.objectLifetime ) + console.log( "Disposing signaling manager" ); + for( const [ /*idCategory*/, signalingCategory ] + of Object.entries( this.mapCategories ) ) + signalingCategory.dispose(); + this.mapCategories = {}; + super.dispose(); + } + allCategoryIDs() { + return Object.keys( this.mapCategories ); + } + categoryGet( idCategory, isAutoAlloc ) { + try { + idCategory = "" + ( idCategory + ? idCategory.toString() : settings.rtcSpace.defaultSpaceCategory ); + isAutoAlloc = ( isAutoAlloc == null || isAutoAlloc == undefined ) + ? true : ( !!isAutoAlloc ); + let signalingCategory = null; + if( idCategory in this.mapCategories ) + signalingCategory = this.mapCategories[idCategory]; + else if( isAutoAlloc ) { + this.mapCategories["" + idCategory] = + signalingCategory = + new SignalingCategory( "" + idCategory, this ); + } + return signalingCategory; + } catch ( err ) { + if( settings.logging.net.signaling.error ) + console.warn( "Signaling category retrieval error:", err ); + return null; + } + } + categoryRemove( idCategory ) { + idCategory = "" + ( idCategory + ? idCategory.toString() : settings.rtcSpace.defaultSpaceName ); + if( idCategory in this.mapCategories ) { + const signalingCategory = this.mapCategories[idCategory]; + signalingCategory.dispose(); + return true; + } + return false; + } +}; + +const gDefaultSignalingManager = new SignalingManager(); + +class SignalingServer extends EventDispatcher { + // eslint-disable-next-line max-lines-per-function + constructor( acceptor, signalingManager ) { + super(); + if( acceptor == null || + acceptor == undefined || + typeof acceptor != "object" ) + throw new Error( "Cannot create test server on bad acceptor" ); + this.acceptor = acceptor; + this.signalingManager = signalingManager || gDefaultSignalingManager; + const self = this; + // eslint-disable-next-line max-lines-per-function + acceptor.on( "connection", function( eventData ) { + const socket = eventData.socket; + if( ( ! ( "remoteAddress" in eventData ) ) || + eventData.remoteAddress == null || + eventData.remoteAddress == undefined ) + socket.strSavedRemoteAddress = socket.constructor.name; + else + socket.strSavedRemoteAddress = "" + eventData.remoteAddress; + socket.signalingClient = null; // not impersonated yet + if( settings.logging.net.signaling.connect ) { + console.log( + "New signaling server connection \"" + + socket.strSavedRemoteAddress + "\"" + ); + } + socket.signalingAuthInfo = { + isAuthorized: false, + idCategory: null, + idSpaceSpace: null, + idRtcParticipant: null + }; + let _offAllPipeEventListeners = null; + let _onPipeClose = function() { + if( settings.logging.net.signaling.disconnect ) { + console.warn( + "Signaling client socket closed \"" + + socket.strSavedRemoteAddress + "\"" + ); + } + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + if( socket.signalingClient ) { + socket.signalingClient.onPipeClose( socket ); + socket.signalingClient = null; + } + }; + let _onPipeError = function( eventData ) { + if( settings.logging.net.signaling.error ) { + console.warn( + "Socket error \"" + socket.strSavedRemoteAddress + "\"" + ); + } + if( _offAllPipeEventListeners ) { + _offAllPipeEventListeners(); + _offAllPipeEventListeners = null; + } + if( socket.signalingClient ) { + socket.signalingClient.onPipeError( socket ); + socket.signalingClient = null; + } + }; + // eslint-disable-next-line max-lines-per-function + let _onPipeMessage = function( eventData ) { + if( settings.logging.net.signaling.rawMessage ) { + console.log( + "Signaling client socket \"" + eventData.strSavedRemoteAddress + + "\" raw message", eventData ); + } + const joMessage = eventData.message; + if( settings.logging.net.signaling.message ) { + console.log( + "Signaling client socket \"" + socket.strSavedRemoteAddress + + "\" message ", joMessage ); + } + let signalingCategory = null; + let signalingSpace = null; + let signalingClient = socket.signalingClient; + if( signalingClient ) { + signalingSpace = signalingClient.signalingSpace; + if( signalingSpace ) + signalingCategory = signalingSpace.signalingCategory; + } + let joAnswer = null; + let isForceDisconnect = false; + try { + switch ( joMessage.method ) { + case "signalingListSpaces": { + joAnswer = utils.prepareAnswerJSON( joMessage ); + joAnswer.arrSpaces = self.gamingSpaceManager.allNames(); + } break; + case "signalingImpersonate": { + const idRtcParticipant = joMessage.idRtcParticipant; + if( ( !idRtcParticipant ) || + typeof idRtcParticipant != "string" || + idRtcParticipant.length <= 0 + ) { + isForceDisconnect = true; + throw new Error( "Bad impersonate call data, " + + "no valid signaling *somebody* ID provided" ); + } + + const strRole = joMessage.role; + if( ( !strRole ) || + typeof strRole != "string" || + strRole.length <= 0 || + ( ! ( strRole == "creator" || strRole == "joiner" ) ) + ) { + isForceDisconnect = true; + throw new Error( "Bad impersonate call data, " + + "no valid signaling *somebody* role provided" ); + } + + const idCategory = joMessage.idCategory; + if( ( !idCategory ) || + typeof idCategory != "string" || + idCategory.length <= 0 + ) { + isForceDisconnect = true; + throw new Error( "Bad impersonate call data, " + + "no valid signaling space category provided" ); + } + signalingCategory = self.signalingManager.categoryGet( idCategory, true ); + if( ! signalingCategory ) { + isForceDisconnect = true; + throw new Error( `Bad impersonate call data, ` + + `cannot get/alloc signaling category with ${idCategory} name` ); + } + + const idSpace = joMessage.idSpace; + if( ( !idSpace ) || typeof idSpace != "string" || idSpace.length <= 0 ) { + isForceDisconnect = true; + throw new Error( "Bad impersonate call data, " + + "no valid signaling space name provided" ); + } + signalingSpace = signalingCategory.spaceGet( idSpace, true ); + if( ! signalingSpace ) { + isForceDisconnect = true; + throw new Error( `Bad impersonate call data, ` + + `cannot get/alloc signaling space with ${idSpace} name` ); + } + + if( signalingSpace.clientGet( idRtcParticipant ) != null ) { + isForceDisconnect = true; + throw new Error( `*Somebody* ${idRtcParticipant} is already ` + + `in ${idSpace} signaling space` ); + } + + if( strRole == "creator" && + signalingSpace.idSomebodyCreator != "" && + signalingSpace.idSomebodyCreator != idRtcParticipant + ) { + throw new Error( `*Somebody* ${idRtcParticipant} is already ` + + `in ${idSpace} attempted to impersonate as creator while ` + + `other creator already exist` ); + } + + signalingClient = + new SignalingClient( + "" + idRtcParticipant, + "" + strRole, + signalingSpace, + socket + ); + if( settings.logging.net.signaling.impersonate ) { + isForceDisconnect = true; + console.log( + "Signaling client socket \"" + socket.strSavedRemoteAddress + + "\" was impersonated as \"" + idRtcParticipant + + "\" in signaling space \"" + signalingSpace.idSpace + "\"" + ); + } + socket.signalingClient = signalingClient; + socket.signalingAuthInfo.isAuthorized = true; + socket.signalingAuthInfo.idCategory = "" + idCategory; + socket.signalingAuthInfo.idSpaceSpace = "" + idSpace; + socket.signalingAuthInfo.idRtcParticipant = "" + idRtcParticipant; + joAnswer = utils.prepareAnswerJSON( joMessage ); // successful answer + joAnswer.signalingAuthInfo = + JSON.parse( JSON.stringify( socket.signalingAuthInfo ) ); + } break; + case "signalingPublishOffer": { + if( ! ( signalingClient && signalingSpace && signalingCategory ) ) { + throw new Error( "only connected signaling clients " + + "can publish offers" ); + } + if( ! ( signalingClient.isCreator ) ) + throw new Error( "only creator can publish offers" ); + const joOfferInfo = { + // "ts": some time stamp + "offer": joMessage.offer, + "idOffer": 0 + joMessage.idOffer + }; + signalingSpace.arrPublishedOffers.push( joOfferInfo ); + if( settings.logging.net.signaling.publishOffer ) { + console.log( + "Signaling client socket \"" + socket.strSavedRemoteAddress + + "\" impersonated as \"" + signalingClient.idRtcParticipant + + "\" in signaling space \"" + signalingSpace.idSpace + + "\" did published creator offer:", joOfferInfo + ); + } + joAnswer = utils.prepareAnswerJSON( joMessage ); // successful answer + } break; + case "signalingFetchOffer": { + if( ! ( signalingClient && signalingSpace && signalingCategory ) ) { + throw new Error( "only connected signaling clients " + + "can fetch published offers" ); + } + signalingClient.offerDiscoveryStart( joMessage ); + } break; + case "signalingPublishAnswer": { + if( ! ( signalingClient && signalingSpace && signalingCategory ) ) { + throw new Error( "only connected signaling clients " + + "can publish offer answers" ); + } + const connectedServerCreator = + signalingSpace.clientGet( joMessage.idSomebodyCreator ); + if( ! connectedServerCreator ) { + throw new Error( `answer published with invalid server ` + + `holder reference: ${joMessage.idSomebodyCreator}` ); + } + const joForwardMessage = JSON.parse( JSON.stringify( joMessage ) ); + joForwardMessage.idSomebody_joiner = "" + signalingClient.idRtcParticipant; + // re-send it to server holder, joiner *somebody* ID is added + connectedServerCreator.socket.send( joForwardMessage ); + // no answer so far(( + } break; + default: { + joAnswer = utils.prepareAnswerJSON( joMessage ); + joAnswer.error = "Unhandled message"; + joAnswer.joMessage = joMessage; // send it back )) + if( settings.logging.net.signaling.error ) { + console.warn( + "Signaling client socket \"" + socket.strSavedRemoteAddress + + "\" unhandled message", joMessage + ); + } + } break; + } // switch( joMessage.method ) + } catch ( err ) { + if( settings.logging.net.signaling.error ) + console.warn( "Server method", joMessage.method, "RPC exception:", err ); + joAnswer = utils.prepareAnswerJSON( joMessage ); + joAnswer.error = "" + err.toString(); + } + if( joAnswer != null && joAnswer != undefined ) { + if( typeof joAnswer.error == "string" && joAnswer.error.length > 0 ) { + if( settings.logging.net.signaling.error ) { + console.warn( + "Signaling client socket \"" + socket.strSavedRemoteAddress + + "\" error answer", joAnswer + ); + } + } else if( settings.logging.net.signaling.message ) { + console.log( + "Signaling client socket \"" + socket.strSavedRemoteAddress + + " answer", joAnswer + ); + } + socket.send( joAnswer, true ); // isFlush=true always in signaling server + if( isForceDisconnect ) + socket.disconnect(); + } + if( signalingSpace ) + signalingSpace.autoDispose(); + if( signalingCategory ) + signalingCategory.autoDispose(); + }; + _offAllPipeEventListeners = function() { + if( _onPipeClose ) { + socket.off( "close", _onPipeClose ); + _onPipeClose = null; + } + if( _onPipeError ) { + socket.off( "error", _onPipeError ); + _onPipeError = null; + } + if( _onPipeMessage ) { + socket.off( "message", _onPipeMessage ); + _onPipeMessage = null; + } + let signalingSpace = null; + const signalingClient = socket.signalingClient; + if( signalingClient ) { + signalingSpace = signalingClient.signalingSpace; + if( settings.logging.net.signaling.disconnect ) { + console.log( + "Handling connection close for signaling client \"" + + signalingClient.idRtcParticipant + "\"" + ); + } + if( signalingSpace ) + signalingSpace.clientRemove( signalingClient.idRtcParticipant ); + + signalingClient.dispose(); + } + socket.signalingClient = null; + }; + socket.on( "close", _onPipeClose ); + socket.on( "error", _onPipeError ); + socket.on( "message", _onPipeMessage ); + } ); + this.dispatchEvent( + new UniversalDispatcherEvent( + "initialized", + { "detail": { "ref": this } } ) ); + } + dispose() { + this.isDisposing = true; + super.dispose(); + } +}; + +const protoName = settings.net.secure ? "WSS" : "WS"; +if( settings.logging.net.signaling.generic ) + console.log( protoName + " signaling server will start" ); +const key = settings.net.secure + ? fs.readFileSync( "./self-signed/self-signed-key.pem", "utf8" ) : null; +const cert = settings.net.secure + ? fs.readFileSync( "./self-signed/self-signed-cert.pem", "utf8" ) : null; +let acceptor = + new networkLayer.WebSocketServerAcceptor( + settings.net.ports.signaling, + key, + cert + ); +let signalingServer = new SignalingServer( acceptor ); +signalingServer.on( "initialized", function() { + if( settings.logging.net.signaling.generic ) + console.log( protoName + " signaling server did started" ); +} ); +signalingServer.on( "dispose", function() { + if( settings.logging.net.signaling.generic ) + console.log( protoName + " signaling server did stopped" ); +} ); + +let gFlagShouldExit = false, gFlagProcessExitWasRequested = false; +function exitIfNeeded() { + if( ! gFlagShouldExit ) + return; + if( gFlagProcessExitWasRequested ) + return; + // ensure components stopped here, if needed + gFlagProcessExitWasRequested = true; + console.log( "App will exit" ); + process.exit( 0 ); +} + +process.on( "SIGINT", function() { + console.warn( "\nApp did caught interrupt signal " ); + // stop components here, if needed + if( signalingServer ) { + console.log( "Will stop signaling server" ); + signalingServer.dispose(); + signalingServer = null; + console.log( "Did stopped signaling server" ); + } + if( acceptor ) { + console.log( "Will stop signaling acceptor" ); + acceptor.dispose(); + acceptor = null; + console.log( "Did stopped signaling acceptor" ); + } + gFlagShouldExit = true; + + exitIfNeeded(); + gFlagShouldExit = true; +} ); diff --git a/test/unitTests/testSocketTypes.mjs b/test/unitTests/testSocketTypes.mjs new file mode 100644 index 00000000..38ec2198 --- /dev/null +++ b/test/unitTests/testSocketTypes.mjs @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE COOL SOCKET + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file testSocketTypes.mjs + * @copyright SKALE Labs 2019-Present + */ + +import * as path from "path"; +import * as url from "url"; +import * as networkLayer from "../../src/build/socket.js"; +import { TestSocketServer } from "./testSocketServer.mjs"; +import { Worker } from "worker_threads"; +import { settings } from "../../src/build/socketSettings.js"; +import * as ws from "ws"; +import * as threadInfo from "../../src/build/threadInfo.js"; + +const __dirname = path.dirname( url.fileURLToPath( import.meta.url ) ); + +const joTestMessage = { "method": "echo", "message": "Please echo this message!" }; + +async function testLocal() { + console.log( "Local test" ); + const strEndPoint = "local_endpoint"; + const acceptor = new networkLayer.LocalSocketServerAcceptor( strEndPoint ); + const server = new TestSocketServer( acceptor ); + const client = new networkLayer.LocalSocketClientPipe( strEndPoint ); + client.on( "message", function( eventData ) { + const joMessage = eventData.message; + console.log( "CLIENT <<<", JSON.stringify( joMessage ) ); + client.disconnect(); + console.log( " " ); + } ); + await threadInfo.sleep( 1 ); + console.log( "CLIENT >>>", JSON.stringify( joTestMessage ) ); + client.send( joTestMessage ); + await threadInfo.sleep( 100 ); + const joReturnValue = { + server: server, + client: client + }; + return joReturnValue; +} + +async function testWorker() { + console.log( "Worker test" ); + const url = "local_worker_server"; + const worker = + new Worker( + path.join( __dirname, "testSocketWorker.mjs" ), + { "type": "module" } + ); + console.log( "Will connect to " + url ); + worker.on( "message", jo => { + if( networkLayer.outOfWorkerAPIs.onMessage( worker, jo ) ) + return; + } ); + const client = new networkLayer.OutOfWorkerSocketClientPipe( url, worker ); + client.on( "message", function( eventData ) { + const joMessage = eventData.message; + console.log( "CLIENT <<<", JSON.stringify( joMessage ) ); + client.disconnect(); + worker.terminate(); + console.log( " " ); + } ); + await threadInfo.sleep( 100 ); + console.log( "CLIENT >>>", JSON.stringify( joTestMessage ) ); + client.send( joTestMessage ); + await threadInfo.sleep( 100 ); + const joReturnValue = { + worker: worker, + client: client + }; + return joReturnValue; +} + +async function testWS() { + console.log( "Web socket test" ); + networkLayer.setWsModule( ws ); + const nPort = 33123; + const url = + ( settings.net.secure ? "wss" : "ws" ) + + "://127.0.0.1:" + nPort; + const key = settings.net.secure + ? fs.readFileSync( "./self-signed/self-signed-key.pem", "utf8" ) : null; + const cert = settings.net.secure + ? fs.readFileSync( "./self-signed/self-signed-cert.pem", "utf8" ) : null; + const acceptor = new networkLayer.WebSocketServerAcceptor( nPort, key, cert ); + const server = new TestSocketServer( acceptor ); + const client = new networkLayer.WebSocketClientPipe( url ); + client.on( "message", function( eventData ) { + const joMessage = eventData.message; + console.log( "CLIENT <<<", JSON.stringify( joMessage ) ); + client.disconnect(); + console.log( " " ); + } ); + await threadInfo.sleep( 100 ); + console.log( "CLIENT >>>", JSON.stringify( joTestMessage ) ); + client.send( joTestMessage ); + await threadInfo.sleep( 100 ); + const joReturnValue = { + server: server, + client: client + }; + return joReturnValue; +} + +async function test() { + await testLocal(); + await testWorker(); + await testWS(); + process.exit( 0 ); +} +test(); diff --git a/test/unitTests/testSocketWorker.mjs b/test/unitTests/testSocketWorker.mjs new file mode 100644 index 00000000..1f43d6a3 --- /dev/null +++ b/test/unitTests/testSocketWorker.mjs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * @license + * SKALE COOL SOCKET + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file testSocketWorker.mjs + * @copyright SKALE Labs 2019-Present + */ + +import * as networkLayer from "../../src/build/socket.js"; +import { TestSocketServer } from "./testSocketServer.mjs"; +import { + parentPort + //, workerData +} from "worker_threads"; + +parentPort.on( "message", jo => { + if( networkLayer.inWorkerAPIs.onMessage( jo ) ) + return; +} ); + +function doSendMessage( type, endpoint, workerUUID, data ) { + const jo = networkLayer.socketReceivedDataReverseMarshall( data ); + const joSend = { + "workerMessageType": + ( type && typeof type == "string" && type.length > 0 ) + ? type : "inWorkerMessage", + "workerEndPoint": endpoint, + "workerUUID": workerUUID, + "data": jo + }; + parentPort.postMessage( networkLayer.socketSentDataMarshall( joSend ) ); +} + +const url = "local_worker_server"; +const acceptor = new networkLayer.InWorkerSocketServerAcceptor( url, doSendMessage ); +const server = new TestSocketServer( acceptor ); +server.on( "dispose", function() { console.log( "disposed in-worker server" ); } ); diff --git a/test/yarn.lock b/test/yarn.lock new file mode 100644 index 00000000..bb3c0a89 --- /dev/null +++ b/test/yarn.lock @@ -0,0 +1,2111 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +abbrev@1.0.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" + integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q== + +acorn-jsx@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + integrity sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg== + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" + integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + +array-includes@^3.1.6: + version "3.1.7" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" + integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-string "^1.0.7" + +array.prototype.findlastindex@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" + integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + +array.prototype.flat@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async@1.x: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + integrity sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w== + +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chokidar@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.3.1" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.0.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-data-property@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" + integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +es-abstract@^1.22.1: + version "1.22.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.2.tgz#90f7282d91d0ad577f505e423e52d4c1d93c1b8a" + integrity sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA== + dependencies: + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.2" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.1" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.12" + is-weakref "^1.0.2" + object-inspect "^1.12.3" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.11" + +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escodegen@1.8.x: + version "1.8.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" + integrity sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A== + dependencies: + esprima "^2.7.1" + estraverse "^1.9.1" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.2.0" + +eslint-config-standard@^14.1.1: + version "14.1.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" + integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== + +eslint-import-resolver-node@^0.3.7: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== + dependencies: + debug "^3.2.7" + +eslint-plugin-es@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" + integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== + dependencies: + eslint-utils "^2.0.0" + regexpp "^3.0.0" + +eslint-plugin-import@^2.20.2: + version "2.28.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz#63b8b5b3c409bfc75ebaf8fb206b07ab435482c4" + integrity sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A== + dependencies: + array-includes "^3.1.6" + array.prototype.findlastindex "^1.2.2" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.8.0" + has "^1.0.3" + is-core-module "^2.13.0" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.6" + object.groupby "^1.0.0" + object.values "^1.1.6" + semver "^6.3.1" + tsconfig-paths "^3.14.2" + +eslint-plugin-node@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" + integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== + dependencies: + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-promise@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz#61485df2a359e03149fdafc0a68b0e030ad2ac45" + integrity sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ== + +eslint-plugin-standard@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz#0c3bf3a67e853f8bbbc580fb4945fbf16f41b7c5" + integrity sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ== + +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@2.7.x, esprima@^2.7.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + integrity sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" + integrity sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA== + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.1: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob-parent@^5.0.0, glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + integrity sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA== + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +handlebars@^4.0.1: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.1: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +import-fresh@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +internal-slot@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== + dependencies: + get-intrinsic "^1.2.0" + has "^1.0.3" + side-channel "^1.0.4" + +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" + integrity sha512-nMtdn4hvK0HjUlzr1DrKSUY8ychprt8dzHOgY2KXsIhHu5PuQQEOTM27gV9Xblyon7aUH/TSFIjRHEODF/FRPg== + dependencies: + abbrev "1.0.x" + async "1.x" + escodegen "1.8.x" + esprima "2.7.x" + glob "^5.0.15" + handlebars "^4.0.1" + js-yaml "3.x" + mkdirp "0.5.x" + nopt "3.x" + once "1.x" + resolve "1.1.x" + supports-color "^3.1.0" + which "^1.1.1" + wordwrap "^1.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@3.x, js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" + integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== + dependencies: + argparse "^2.0.1" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash@^4.17.14, lodash@^4.17.19: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +"minimatch@2 || 3", minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@0.5.x, mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mocha@^8.2.1: + version "8.4.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff" + integrity sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.1" + debug "4.3.1" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.1.6" + growl "1.10.5" + he "1.2.0" + js-yaml "4.0.0" + log-symbols "4.0.0" + minimatch "3.0.4" + ms "2.1.3" + nanoid "3.1.20" + serialize-javascript "5.0.1" + strip-json-comments "3.1.1" + supports-color "8.1.1" + which "2.0.2" + wide-align "1.1.3" + workerpool "6.1.0" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +nanoid@3.1.20: + version "3.1.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" + integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +nopt@3.x: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + integrity sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-inspect@^1.12.3, object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.fromentries@^2.0.6: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +object.groupby@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" + integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + +object.values@^1.1.6: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +once@1.x, once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.1, optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== + dependencies: + picomatch "^2.2.1" + +regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regexpp@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@1.1.x: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg== + +resolve@^1.10.1, resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.5.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^6.1.0, semver@^6.1.2, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +serialize-javascript@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" + integrity sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA== + dependencies: + amdefine ">=0.0.4" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-json-comments@3.1.1, strip-json-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^3.1.0: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A== + dependencies: + has-flag "^1.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tsconfig-paths@^3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + +uglify-js@^3.1.4: + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.4.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" + integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" + integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + +which@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +which@^1.1.1, which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +word-wrap@~1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +workerpool@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" + integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..d99b7bec --- /dev/null +++ b/yarn.lock @@ -0,0 +1,424 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/debug@^4.0.0": + version "4.1.12" + resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/mdast@^4.0.0": + version "4.0.3" + resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz" + integrity sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg== + dependencies: + "@types/unist" "*" + +"@types/ms@*": + version "0.7.34" + resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz" + integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== + +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz" + integrity sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ== + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +debug@^4.0.0: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decode-named-character-reference@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz" + integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg== + dependencies: + character-entities "^2.0.0" + +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +devlop@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + +mdast-util-from-markdown@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz" + integrity sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz" + integrity sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz" + integrity sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + +micromark-core-commonmark@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz" + integrity sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz" + integrity sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz" + integrity sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz" + integrity sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz" + integrity sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz" + integrity sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz" + integrity sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz" + integrity sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz" + integrity sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz" + integrity sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz" + integrity sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz" + integrity sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz" + integrity sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA== + +micromark-util-html-tag-name@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz" + integrity sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz" + integrity sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz" + integrity sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz" + integrity sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz" + integrity sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz" + integrity sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw== + +micromark-util-types@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz" + integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w== + +micromark@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz" + integrity sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-stringify@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz" + integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-to-markdown "^2.0.0" + unified "^11.0.0" + +remark@^15.0.1: + version "15.0.1" + resolved "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz" + integrity sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A== + dependencies: + "@types/mdast" "^4.0.0" + remark-parse "^11.0.0" + remark-stringify "^11.0.0" + unified "^11.0.0" + +trough@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz" + integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g== + +unified@^11.0.0: + version "11.0.4" + resolved "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz" + integrity sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +vfile-message@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz" + integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz" + integrity sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +zwitch@^2.0.0: + version "2.0.4" + resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==