diff --git a/.dockerignore b/.dockerignore
index 37dd96d2..edadf75c 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -11,5 +11,10 @@ docs/
networks/
scratch/
+# Ignore build cache directories to avoid
+# errors when addings these to docker images
+build/.cache
+build/.golangci-lint
+
go.work
go.work.sum
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000..a5fcd280
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,3 @@
+# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
+# Global rule:
+* @rhuairahrighairidh @karzak @pirtleshell @drklee3 @nddeluca @DracoLi @evgeniy-scherbina @sesheffield @boodyvo @lbayas
\ No newline at end of file
diff --git a/.github/scripts/seed-internal-testnet.sh b/.github/scripts/seed-internal-testnet.sh
index 19402ad4..d94a62b0 100755
--- a/.github/scripts/seed-internal-testnet.sh
+++ b/.github/scripts/seed-internal-testnet.sh
@@ -83,6 +83,31 @@ TETHER_USDT_CONTRACT_DEPLOY=$(npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NA
TETHER_USDT_CONTRACT_ADDRESS=${TETHER_USDT_CONTRACT_DEPLOY: -42}
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$TETHER_USDT_CONTRACT_ADDRESS" 0x6767114FFAA17C6439D7AEA480738B982CE63A02 1000000000000
+# deploy and fund axlBNB ERC20 contract
+AXL_BNB_CONTRACT_DEPLOY=$(npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" deploy-erc20 "axlBNB" axlBNB 18)
+AXL_BNB_CONTRACT_ADDRESS=${AXL_BNB_CONTRACT_DEPLOY: -42}
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_BNB_CONTRACT_ADDRESS" 0x6767114FFAA17C6439D7AEA480738B982CE63A02 1000000000000000000000
+
+# deploy and fund axlBUSD ERC20 contract
+AXL_BUSD_CONTRACT_DEPLOY=$(npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" deploy-erc20 "axlBUSD" axlBUSD 18)
+AXL_BUSD_CONTRACT_ADDRESS=${AXL_BUSD_CONTRACT_DEPLOY: -42}
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_BUSD_CONTRACT_ADDRESS" 0x6767114FFAA17C6439D7AEA480738B982CE63A02 1000000000000000000000
+
+# deploy and fund axlXRPB ERC20 contract
+AXL_XRPB_CONTRACT_DEPLOY=$(npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" deploy-erc20 "axlXRPB" axlXRPB 18)
+AXL_XRPB_CONTRACT_ADDRESS=${AXL_XRPB_CONTRACT_DEPLOY: -42}
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_XRPB_CONTRACT_ADDRESS" 0x6767114FFAA17C6439D7AEA480738B982CE63A02 1000000000000000000000
+
+# deploy and fund axlBTC ERC20 contract
+AXL_BTCB_CONTRACT_DEPLOY=$(npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" deploy-erc20 "axlBTCB" axlBTCB 18)
+AXL_BTCB_CONTRACT_ADDRESS=${AXL_BTCB_CONTRACT_DEPLOY: -42}
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_BTCB_CONTRACT_ADDRESS" 0x6767114FFAA17C6439D7AEA480738B982CE63A02 1000000000000000000000
+
+# deploy and fund native wBTC ERC20 contract
+WBTC_CONTRACT_DEPLOY=$(npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" deploy-erc20 "wBTC" wBTC 8)
+WBTC_CONTRACT_ADDRESS=${WBTC_CONTRACT_DEPLOY: -42}
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$WBTC_CONTRACT_ADDRESS" 0x6767114FFAA17C6439D7AEA480738B982CE63A02 100000000000000000
+
# seed some evm wallets
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_WBTC_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 10000000000000
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$MULTICHAIN_wBTC_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 10000000000000
@@ -91,6 +116,11 @@ npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$wETH_CONTRAC
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_USDC_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 100000000000
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$MULTICHAIN_USDT_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 100000000000
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$TETHER_USDT_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 1000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_BNB_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 1000000000000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_BUSD_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 1000000000000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_XRPB_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 1000000000000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_BTCB_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 1000000000000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$WBTC_CONTRACT_ADDRESS" "$DEV_TEST_WALLET_ADDRESS" 10000000000000
# seed webapp E2E whale account
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_WBTC_CONTRACT_ADDRESS" "$WEBAPP_E2E_WHALE_ADDRESS" 100000000000000
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$MULTICHAIN_wBTC_CONTRACT_ADDRESS" "$WEBAPP_E2E_WHALE_ADDRESS" 10000000000000
@@ -99,6 +129,11 @@ npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$wETH_CONTRAC
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_USDC_CONTRACT_ADDRESS" "$WEBAPP_E2E_WHALE_ADDRESS" 10000000000000
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$MULTICHAIN_USDT_CONTRACT_ADDRESS" "$WEBAPP_E2E_WHALE_ADDRESS" 1000000000000
npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$TETHER_USDT_CONTRACT_ADDRESS" "$WEBAPP_E2E_WHALE_ADDRESS" 1000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_BNB_CONTRACT_ADDRESS" "$WEBAPP_E2E_WHALE_ADDRESS" 10000000000000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_BUSD_CONTRACT_ADDRESS" "$WEBAPP_E2E_WHALE_ADDRESS" 10000000000000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_BTCB_CONTRACT_ADDRESS" "$WEBAPP_E2E_WHALE_ADDRESS" 10000000000000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$AXL_XRPB_CONTRACT_ADDRESS" "$WEBAPP_E2E_WHALE_ADDRESS" 10000000000000000000000
+npx hardhat --network "${ERC20_DEPLOYER_NETWORK_NAME}" mint-erc20 "$WBTC_CONTRACT_ADDRESS" "$WBTC_CONTRACT_ADDRESS" 10000000000000
# give dev-wallet enough delegation power to pass proposals by itself
@@ -138,7 +173,7 @@ PARAM_CHANGE_PROP_TEMPLATE=$(
{
"subspace": "evmutil",
"key": "EnabledConversionPairs",
- "value": "[{\"kava_erc20_address\":\"MULTICHAIN_USDC_CONTRACT_ADDRESS\",\"denom\":\"erc20/multichain/usdc\"},{\"kava_erc20_address\":\"MULTICHAIN_USDT_CONTRACT_ADDRESS\",\"denom\":\"erc20/multichain/usdt\"},{\"kava_erc20_address\":\"MULTICHAIN_wBTC_CONTRACT_ADDRESS\",\"denom\":\"erc20/multichain/wbtc\"},{\"kava_erc20_address\":\"AXL_USDC_CONTRACT_ADDRESS\",\"denom\":\"erc20/axelar/usdc\"},{\"kava_erc20_address\":\"AXL_WBTC_CONTRACT_ADDRESS\",\"denom\":\"erc20/axelar/wbtc\"},{\"kava_erc20_address\":\"wETH_CONTRACT_ADDRESS\",\"denom\":\"erc20/axelar/eth\"},{\"kava_erc20_address\":\"TETHER_USDT_CONTRACT_ADDRESS\",\"denom\":\"erc20/tether/usdt\"}]"
+ "value": "[{\"kava_erc20_address\":\"MULTICHAIN_USDC_CONTRACT_ADDRESS\",\"denom\":\"erc20/multichain/usdc\"},{\"kava_erc20_address\":\"MULTICHAIN_USDT_CONTRACT_ADDRESS\",\"denom\":\"erc20/multichain/usdt\"},{\"kava_erc20_address\":\"MULTICHAIN_wBTC_CONTRACT_ADDRESS\",\"denom\":\"erc20/multichain/wbtc\"},{\"kava_erc20_address\":\"AXL_USDC_CONTRACT_ADDRESS\",\"denom\":\"erc20/axelar/usdc\"},{\"kava_erc20_address\":\"AXL_WBTC_CONTRACT_ADDRESS\",\"denom\":\"erc20/axelar/wbtc\"},{\"kava_erc20_address\":\"wETH_CONTRACT_ADDRESS\",\"denom\":\"erc20/axelar/eth\"},{\"kava_erc20_address\":\"TETHER_USDT_CONTRACT_ADDRESS\",\"denom\":\"erc20/tether/usdt\"},{\"kava_erc20_address\":\"AXL_BNB_CONTRACT_ADDRESS\",\"denom\":\"bnb\"},{\"kava_erc20_address\":\"AXL_BUSD_CONTRACT_ADDRESS\",\"denom\":\"busd\"},{\"kava_erc20_address\":\"AXL_BTCB_CONTRACT_ADDRESS\",\"denom\":\"btcb\"},{\"kava_erc20_address\":\"AXL_XRPB_CONTRACT_ADDRESS\",\"denom\":\"xrpb\"},{\"kava_erc20_address\":\"WBTC_CONTRACT_ADDRESS\",\"denom\":\"erc20/bitgo/wbtc\"}]"
}
]
}
@@ -155,6 +190,11 @@ finalProposal="${finalProposal/AXL_USDC_CONTRACT_ADDRESS/$AXL_USDC_CONTRACT_ADDR
finalProposal="${finalProposal/AXL_WBTC_CONTRACT_ADDRESS/$AXL_WBTC_CONTRACT_ADDRESS}"
finalProposal="${finalProposal/wETH_CONTRACT_ADDRESS/$wETH_CONTRACT_ADDRESS}"
finalProposal="${finalProposal/TETHER_USDT_CONTRACT_ADDRESS/$TETHER_USDT_CONTRACT_ADDRESS}"
+finalProposal="${finalProposal/AXL_BNB_CONTRACT_ADDRESS/$AXL_BNB_CONTRACT_ADDRESS}"
+finalProposal="${finalProposal/AXL_BUSD_CONTRACT_ADDRESS/$AXL_BUSD_CONTRACT_ADDRESS}"
+finalProposal="${finalProposal/AXL_BTCB_CONTRACT_ADDRESS/$AXL_BTCB_CONTRACT_ADDRESS}"
+finalProposal="${finalProposal/AXL_XRPB_CONTRACT_ADDRESS/$AXL_XRPB_CONTRACT_ADDRESS}"
+finalProposal="${finalProposal/WBTC_CONTRACT_ADDRESS/$WBTC_CONTRACT_ADDRESS}"
# create unique proposal filename
proposalFileName="$(date +%s)-proposal.json"
@@ -185,6 +225,21 @@ sleep $AVG_SECONDS_BETWEEN_BLOCKS
updatedEvmUtilParams=$(curl https://api.app.internal.testnet.us-east.production.kava.io/kava/evmutil/v1beta1/params)
printf "updated evm util module params\n %s" , "$updatedEvmUtilParams"
+# submit a kava token committee proposal
+COMMITTEE_PROP_TEMPLATE=$(
+ cat <<'END_HEREDOC'
+{
+ "@type": "/cosmos.gov.v1beta1.TextProposal",
+ "title": "The next big thing signaling proposal.",
+ "description": "The purpose of this proposal is to signal support/opposition to the next big thing"
+}
+END_HEREDOC
+)
+committeeProposalFileName="$(date +%s)-committee-proposal.json"
+echo "$COMMITTEE_PROP_TEMPLATE" >$committeeProposalFileName
+tokenCommitteeId=4
+kava tx committee submit-proposal "$tokenCommitteeId" "$committeeProposalFileName" --gas auto --gas-adjustment 1.5 --gas-prices 0.01ukava --from god -y
+
# if adding more cosmos coins -> er20s, ensure that the deployment order below remains the same.
# convert 1 HARD to an erc20. doing this ensures the contract is deployed.
kava tx evmutil convert-cosmos-coin-to-erc20 \
diff --git a/.github/workflows/cd-reset-internal-testnet.yml b/.github/workflows/cd-reset-internal-testnet.yml
index 9df50d7a..738fd4c3 100644
--- a/.github/workflows/cd-reset-internal-testnet.yml
+++ b/.github/workflows/cd-reset-internal-testnet.yml
@@ -67,7 +67,6 @@ jobs:
--update-playbook-filename=$PLAYBOOK_NAME \
--chain-id=$CHAIN_ID \
--max-upgrade-batch-size=0 \
- --node-states=Standby \
--wait-for-node-sync-after-upgrade=false
env:
SSM_DOCUMENT_NAME: ${{ inputs.ssm-document-name }}
diff --git a/.github/workflows/cd-seed-chain.yml b/.github/workflows/cd-seed-chain.yml
index 43cf6e87..e00d9332 100644
--- a/.github/workflows/cd-seed-chain.yml
+++ b/.github/workflows/cd-seed-chain.yml
@@ -35,12 +35,16 @@ jobs:
uses: actions/checkout@v4
with:
ref: master
- - name: checkout version of kava used by network
+ - name: get desired version of network
+ id: kava-version
run: |
- git pull -p
- git checkout $(cat ${KAVA_VERSION_FILEPATH})
+ echo "KAVA_VERSION=$(cat ./ci/env/kava-internal-testnet/KAVA.VERSION)" >> $GITHUB_OUTPUT
env:
KAVA_VERSION_FILEPATH: ${{ inputs.kava_version_filepath }}
+ - name: checkout version of kava used by network
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ steps.kava-version.outputs.KAVA_VERSION }}
- name: Set up Go
uses: actions/setup-go@v4
with:
@@ -68,7 +72,7 @@ jobs:
uses: actions/setup-node@v3
with:
cache: npm
- node-version: 18
+ node-version-file: .tool-versions
cache-dependency-path: kava-bridge/contract/package.json
- name: "install ERC20 contract deployment dependencies"
run: "npm install"
diff --git a/.github/workflows/cd-start-chain.yml b/.github/workflows/cd-start-chain.yml
index 02ee3065..4e9644f3 100644
--- a/.github/workflows/cd-start-chain.yml
+++ b/.github/workflows/cd-start-chain.yml
@@ -63,7 +63,6 @@ jobs:
--update-playbook-filename=$PLAYBOOK_NAME \
--chain-id=$CHAIN_ID \
--max-upgrade-batch-size=0 \
- --node-states=Standby \
--wait-for-node-sync-after-upgrade=true
env:
SSM_DOCUMENT_NAME: ${{ inputs.ssm-document-name }}
diff --git a/.github/workflows/ci-default.yml b/.github/workflows/ci-default.yml
index 84806729..5bc2b3a1 100644
--- a/.github/workflows/ci-default.yml
+++ b/.github/workflows/ci-default.yml
@@ -35,6 +35,35 @@ jobs:
run: make test
- name: run e2e tests
run: make docker-build test-e2e
+ fuzz:
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout repo from current commit
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+ - name: Set up Go
+ uses: actions/setup-go@v4
+ with:
+ go-version-file: go.mod
+ cache-dependency-path: |
+ go.sum
+ - name: run fuzz tests
+ run: make test-fuzz
+ ibc-test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout repo from current commit
+ uses: actions/checkout@v4
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version-file: tests/e2e-ibc/go.mod
+ cache-dependency-path: |
+ tests/e2e-ibc/go.sum
+ go.sum
+ - name: run ibc e2e tests
+ run: make test-ibc
validate-internal-testnet-genesis:
runs-on: ubuntu-latest
steps:
@@ -44,16 +73,10 @@ jobs:
id: kava-version
run: |
echo "KAVA_VERSION=$(cat ./ci/env/kava-internal-testnet/KAVA.VERSION)" >> $GITHUB_OUTPUT
- - name: checkout repo from master
+ - name: checkout version of kava that will be deployed if this pr is merged
uses: actions/checkout@v4
with:
- ref: master
- - name: checkout version of kava that will be deployed if this pr is merged
- run: |
- git pull -p
- git checkout $KAVA_VERSION
- env:
- KAVA_VERSION: ${{ steps.kava-version.outputs.KAVA_VERSION }}
+ ref: ${{ steps.kava-version.outputs.KAVA_VERSION }}
- name: Set up Go
uses: actions/setup-go@v4
with:
diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml
index 067f6055..77f8f67a 100644
--- a/.github/workflows/ci-docker.yml
+++ b/.github/workflows/ci-docker.yml
@@ -50,6 +50,17 @@ jobs:
username: ${{ inputs.dockerhub-username }}
password: ${{ secrets.CI_DOCKERHUB_TOKEN }}
+ - name: Go Build Cache for Docker
+ uses: actions/cache@v3
+ with:
+ path: go-build-cache
+ key: ${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.sum') }}
+
+ - name: inject go-build-cache into docker
+ uses: reproducible-containers/buildkit-cache-dance@v2.1.2
+ with:
+ cache-source: go-build-cache
+
# publish to docker hub, tag with short git hash
- name: Build and push (goleveldb)
uses: docker/build-push-action@v5
@@ -89,6 +100,17 @@ jobs:
username: ${{ inputs.dockerhub-username }}
password: ${{ secrets.CI_DOCKERHUB_TOKEN }}
+ - name: Go Build Cache for Docker
+ uses: actions/cache@v3
+ with:
+ path: go-build-cache
+ key: ${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.sum') }}
+
+ - name: inject go-build-cache into docker
+ uses: reproducible-containers/buildkit-cache-dance@v2.1.2
+ with:
+ cache-source: go-build-cache
+
# publish to docker hub, tag with short git hash
- name: Build and push (rocksdb)
uses: docker/build-push-action@v5
diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml
index 7c36c835..6580586c 100644
--- a/.github/workflows/ci-lint.yml
+++ b/.github/workflows/ci-lint.yml
@@ -7,11 +7,25 @@ jobs:
uses: ./.github/workflows/proto.yml
golangci-lint:
runs-on: ubuntu-latest
+ permissions:
+ checks: write # allow write access to checks to allow the action to annotate code in the PR.
steps:
- - uses: actions/checkout@v4
- - name: golangci-lint
- uses: reviewdog/action-golangci-lint@v2
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with: { fetch-depth: 0 }
+ - name: Set up Go
+ uses: actions/setup-go@v4
with:
- github_token: ${{ secrets.github_token }}
- reporter: github-pr-review
- golangci_lint_flags: --timeout 10m
+ go-version-file: go.mod
+ - name: Load Version
+ id: load-version
+ run: |
+ GOLANGCI_VERSION=$(cat .golangci-version)
+ REV=$(git merge-base origin/master HEAD)
+ echo "GOLANGCI_VERSION=$GOLANGCI_VERSION" >> $GITHUB_ENV
+ echo "REV=$REV" >> $GITHUB_ENV
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@v6
+ with:
+ version: ${{ env.GOLANGCI_VERSION }}
+ args: -v -c .golangci.yml --new-from-rev ${{ env.REV }}
diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml
index 8a4750d1..adb6353f 100644
--- a/.github/workflows/ci-master.yml
+++ b/.github/workflows/ci-master.yml
@@ -45,6 +45,9 @@ jobs:
dockerhub-username: kavaops
extra-image-tag: master
secrets: inherit
+ rosetta:
+ uses: ./.github/workflows/ci-rosetta.yml
+ secrets: inherit
post-pipeline-metrics:
uses: ./.github/workflows/metric-pipeline.yml
if: always() # always run so we metric failures and successes
diff --git a/.github/workflows/ci-pr-lint.yml b/.github/workflows/ci-pr-lint.yml
new file mode 100644
index 00000000..3b6685c9
--- /dev/null
+++ b/.github/workflows/ci-pr-lint.yml
@@ -0,0 +1,27 @@
+# this workflow is responsible for ensuring quality titles are given to all PRs
+# for PR checks to pass, the title must follow the Conventional Commits standard
+# https://www.conventionalcommits.org/en/v1.0.0/
+# this workflow was adapted from a similar workflow in https://github.com/cosmos/cosmos-sdk
+name: "Lint PR Title"
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - edited
+ - synchronize
+
+permissions:
+ contents: read
+
+jobs:
+ main:
+ permissions:
+ pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs
+ statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
+ runs-on: ubuntu-latest
+ steps:
+ # https://github.com/marketplace/actions/semantic-pull-request
+ - uses: amannn/action-semantic-pull-request@v5.5.3
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml
index bc8aa955..a562df82 100644
--- a/.github/workflows/ci-release.yml
+++ b/.github/workflows/ci-release.yml
@@ -4,9 +4,6 @@ on:
tags:
- "v[0-9]+.[0-9]+.[0-9]+*"
jobs:
- # run per commit ci checks against released version
- lint-checks:
- uses: ./.github/workflows/ci-lint.yml
# run default ci checks against released version
default-checks:
uses: ./.github/workflows/ci-default.yml
@@ -14,7 +11,7 @@ jobs:
# get the version tag that triggered this workflow
get-version-tag:
# prep version release only if all checks pass
- needs: [lint-checks, default-checks]
+ needs: default-checks
runs-on: ubuntu-latest
outputs:
git-tag: ${{ steps.git-tag.outputs.tag }}
diff --git a/.github/workflows/ci-rocksdb-build.yml b/.github/workflows/ci-rocksdb-build.yml
index c760f7b1..9ce9a2f2 100644
--- a/.github/workflows/ci-rocksdb-build.yml
+++ b/.github/workflows/ci-rocksdb-build.yml
@@ -19,25 +19,3 @@ jobs:
run: bash ${GITHUB_WORKSPACE}/.github/scripts/install-rocksdb.sh
- name: build application
run: make build COSMOS_BUILD_OPTIONS=rocksdb
- test:
- runs-on: ubuntu-latest
- steps:
- - name: install RocksDB dependencies
- run: sudo apt-get update
- && sudo apt-get install -y git make gcc libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
- - name: install RocksDB as shared library
- run: git clone https://github.com/facebook/rocksdb.git
- && cd rocksdb
- && git checkout $ROCKSDB_VERSION
- && sudo make -j$(nproc) install-shared
- && sudo ldconfig
- - name: checkout repo from current commit
- uses: actions/checkout@v4
- with:
- submodules: true
- - name: Set up Go
- uses: actions/setup-go@v4
- with:
- go-version-file: go.mod
- - name: run unit tests
- run: make test-rocksdb
diff --git a/.github/workflows/ci-rosetta.yml b/.github/workflows/ci-rosetta.yml
new file mode 100644
index 00000000..7673707b
--- /dev/null
+++ b/.github/workflows/ci-rosetta.yml
@@ -0,0 +1,16 @@
+name: Dispatch run-rosetta-tests event to rosetta-kava
+
+on:
+ workflow_call:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Dispatch run-rosetta-tests event to rosetta-kava
+ uses: peter-evans/repository-dispatch@v3
+ with:
+ token: ${{ secrets.KAVA_PUBLIC_GITHUB_ACCESS_TOKEN }}
+ repository: Kava-Labs/rosetta-kava
+ event-type: run-rosetta-tests
+ client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'
diff --git a/.gitignore b/.gitignore
index 398d9ecb..e855238c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,9 @@ out
# Ignore build cache dir
build/.cache
+# Ignore make lint cache
+build/.golangci-lint
+
# Ignore installed binaires
build/bin
diff --git a/.golangci-version b/.golangci-version
new file mode 100644
index 00000000..35345c2f
--- /dev/null
+++ b/.golangci-version
@@ -0,0 +1 @@
+v1.59
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 00000000..f4f247cd
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,130 @@
+run:
+ timeout: 20m # set maximum time allowed for the linter to run. If the linting process exceeds this duration, it will be terminated
+ modules-download-mode: readonly # Ensures that modules are not modified during the linting process
+ allow-parallel-runners: true # enables parallel execution of linters to speed up linting process
+
+linters:
+ disable-all: true
+ enable:
+ - asasalint
+ - asciicheck
+ - bidichk
+ - bodyclose
+ - containedctx
+ - contextcheck
+ - decorder
+ - dogsled
+ # - dupl
+ # - dupword
+ - durationcheck
+ - errcheck
+ - errchkjson
+ - errname
+ - errorlint
+ # - exhaustive
+ - exportloopref
+ - funlen
+ - gci
+ - ginkgolinter
+ - gocheckcompilerdirectives
+ # - gochecknoglobals
+ # - gochecknoinits
+ - goconst
+ - gocritic
+ - godox
+ - gofmt
+ # - gofumpt
+ - goheader
+ - goimports
+ - mnd
+ # - gomodguard
+ - goprintffuncname
+ - gosec
+ - gosimple
+ - govet
+ - grouper
+ - importas
+ - ineffassign
+ # - interfacebloat
+ - lll
+ - loggercheck
+ - makezero
+ - mirror
+ - misspell
+ - musttag
+ # - nakedret
+ # - nestif
+ - nilerr
+ # - nilnil
+ # - noctx
+ - nolintlint
+ # - nonamedreturns
+ - nosprintfhostport
+ - prealloc
+ - predeclared
+ - promlinter
+ # - reassign
+ - revive
+ - rowserrcheck
+ - staticcheck
+ # - stylecheck
+ - tagalign
+ # - testpackage
+ # - thelper
+ # - tparallel
+ - typecheck
+ # - unconvert
+ - unparam
+ - unused
+ # - usestdlibvars
+ - wastedassign
+ # - whitespace
+ - wrapcheck
+
+issues:
+ exclude-rules:
+ # Disable funlen for "func Test..." or func (suite *Suite) Test..." type functions
+ # These functions tend to be descriptive and exceed length limits.
+ - source: "^func (\\(.*\\) )?Test"
+ linters:
+ - funlen
+
+linters-settings:
+ errcheck:
+ check-blank: true # check for assignments to the blank identifier '_' when errors are returned
+ check-type-assertions: false # check type assertion
+ errorlint:
+ check-generated: false # disabled linting of generated files
+ default-signifies-exhaustive: false # exhaustive handling of error types
+ exhaustive:
+ default-signifies-exhaustive: false # exhaustive handling of error types
+ gci:
+ sections: # defines the order of import sections
+ - standard
+ - default
+ - localmodule
+ goconst:
+ min-len: 3 # min length for string constants to be checked
+ min-occurrences: 3 # min occurrences of the same constant before it's flagged
+ godox:
+ keywords: # specific keywords to flag for further action
+ - BUG
+ - FIXME
+ - HACK
+ gosec:
+ exclude-generated: true
+ lll:
+ line-length: 120
+ misspell:
+ locale: US
+ ignore-words: expect
+ nolintlint:
+ allow-leading-space: false
+ require-explanation: true
+ require-specific: true
+ prealloc:
+ simple: true # enables simple preallocation checks
+ range-loops: true # enabled preallocation checks in range loops
+ for-loops: false # disables preallocation checks in for loops
+ unparam:
+ check-exported: true # checks exported functions and methods for unused params
diff --git a/.mockery.yaml b/.mockery.yaml
new file mode 100644
index 00000000..746c20a4
--- /dev/null
+++ b/.mockery.yaml
@@ -0,0 +1,16 @@
+# Generate EXPECT() methods, type-safe methods to generate call expectations
+with-expecter: true
+
+# Generate mocks in adjacent mocks directory to the interfaces
+dir: "{{.InterfaceDir}}/mocks"
+mockname: "Mock{{.InterfaceName}}"
+outpkg: "mocks"
+filename: "Mock{{.InterfaceName}}.go"
+
+packages:
+ github.com/0glabs/0g-chain/x/precisebank/types:
+ # package-specific config
+ config:
+ interfaces:
+ AccountKeeper:
+ BankKeeper:
diff --git a/.tool-versions b/.tool-versions
index a0274e41..622b42f0 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
-golang 1.21
-nodejs 18.16.0
+golang 1.21.9
+nodejs 20.16.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d6b9038..227fbb9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,16 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [v0.26.0]
+### Features
+- (precisebank) [#1906] Add new `x/precisebank` module with bank decimal extension for EVM usage.
+- (cli) [#1922] Add `iavlviewer` CLI command for low-level iavl db debugging.
+
+### Improvements
+- (rocksdb) [#1903] Bump cometbft-db dependency for use with rocksdb v8.10.0
+- (deps) [#1988] Bump cometbft to v0.37.9-kava.1
+
+## [v0.26.0]
+
### Features
- (cli) [#1785] Add `shard` CLI command to support creating partitions of data for standalone nodes
@@ -330,6 +340,10 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md).
- [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run
large-scale simulations remotely using aws-batch
+[#1988]: https://github.com/Kava-Labs/kava/pull/1988
+[#1922]: https://github.com/Kava-Labs/kava/pull/1922
+[#1906]: https://github.com/Kava-Labs/kava/pull/1906
+[#1903]: https://github.com/Kava-Labs/kava/pull/1903
[#1846]: https://github.com/Kava-Labs/kava/pull/1846
[#1848]: https://github.com/Kava-Labs/kava/pull/1848
[#1839]: https://github.com/Kava-Labs/kava/pull/1839
@@ -392,8 +406,9 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md).
[#750]: https://github.com/Kava-Labs/kava/pull/750
[#751]: https://github.com/Kava-Labs/kava/pull/751
[#780]: https://github.com/Kava-Labs/kava/pull/780
-[unreleased]: https://github.com/Kava-Labs/kava/compare/v0.25.0...HEAD
-[v0.25.0]: https://github.com/Kava-Labs/kava/compare/v0.25.0...v0.24.3
+[unreleased]: https://github.com/Kava-Labs/kava/compare/v0.26.0...HEAD
+[v0.26.0]: https://github.com/Kava-Labs/kava/compare/v0.25.0...v0.26.0
+[v0.25.0]: https://github.com/Kava-Labs/kava/compare/v0.24.3...v0.25.0
[v0.24.3]: https://github.com/Kava-Labs/kava/compare/v0.24.3...v0.24.1
[v0.24.1]: https://github.com/Kava-Labs/kava/compare/v0.24.1...v0.24.0
[v0.24.0]: https://github.com/Kava-Labs/kava/compare/v0.24.0...v0.23.2
diff --git a/Dockerfile-rocksdb b/Dockerfile-rocksdb
index 0a7fc447..d3dfd598 100644
--- a/Dockerfile-rocksdb
+++ b/Dockerfile-rocksdb
@@ -1,23 +1,6 @@
-FROM golang:1.20-bullseye AS chain-builder
+FROM kava/rocksdb:v8.10.1-go1.21 AS kava-builder
-# Set up dependencies
-RUN apt-get update \
- && apt-get install -y git make gcc libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev \
- && rm -rf /var/lib/apt/lists/*
-
-# Set working directory for the build
-WORKDIR /root
-# default home directory is /root
-
-# install rocksdb
-ARG rocksdb_version=v8.10.0
-ENV ROCKSDB_VERSION=$rocksdb_version
-
-RUN git clone https://github.com/facebook/rocksdb.git \
- && cd rocksdb \
- && git checkout $ROCKSDB_VERSION \
- && make -j$(nproc) install-shared \
- && ldconfig
+RUN apt-get update
WORKDIR /root/0gchain
# Copy dependency files first to facilitate dependency caching
diff --git a/Dockerfile-rocksdb-base b/Dockerfile-rocksdb-base
new file mode 100644
index 00000000..30b3de26
--- /dev/null
+++ b/Dockerfile-rocksdb-base
@@ -0,0 +1,22 @@
+# published to https://hub.docker.com/repository/docker/kava/rocksdb/tags
+# docker buildx build --platform linux/amd64,linux/arm64 -t kava/rocksdb:v8.10.1-go1.21 -f Dockerfile-rocksdb-base . --push
+FROM golang:1.21-bullseye
+
+# Set up dependencies
+RUN apt-get update \
+ && apt-get install -y git make gcc libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set working directory for the build
+WORKDIR /root
+# default home directory is /root
+
+# install rocksdb
+ARG rocksdb_version=v8.10.0
+ENV ROCKSDB_VERSION=$rocksdb_version
+
+RUN git clone https://github.com/facebook/rocksdb.git \
+ && cd rocksdb \
+ && git checkout $ROCKSDB_VERSION \
+ && make -j$(nproc) install-shared \
+ && ldconfig
diff --git a/Makefile b/Makefile
index f556645c..9ea5e809 100644
--- a/Makefile
+++ b/Makefile
@@ -105,6 +105,8 @@ include $(BUILD_DIR)/deps.mk
include $(BUILD_DIR)/proto.mk
include $(BUILD_DIR)/proto-deps.mk
+include $(BUILD_DIR)/lint.mk
+
#export GO111MODULE = on
# process build tags
build_tags = netgo
@@ -208,6 +210,14 @@ build-release: go.sum
build-linux: go.sum
LEDGER_ENABLED=false GOOS=linux GOARCH=amd64 $(MAKE) build
+# build on rocksdb-backed kava on macOS with shared libs from brew
+# this assumes you are on macOS & these deps have been installed with brew:
+# rocksdb, snappy, lz4, and zstd
+# use like `make build-rocksdb-brew COSMOS_BUILD_OPTIONS=rocksdb`
+build-rocksdb-brew:
+ export CGO_CFLAGS := -I$(shell brew --prefix rocksdb)/include
+ export CGO_LDFLAGS := -L$(shell brew --prefix rocksdb)/lib -lrocksdb -lstdc++ -lm -lz -L$(shell brew --prefix snappy)/lib -L$(shell brew --prefix lz4)/lib -L$(shell brew --prefix zstd)/lib
+
install: go.sum
$(GO_BIN) install -mod=readonly $(BUILD_FLAGS) $(MAIN_ENTRY)
@@ -234,13 +244,6 @@ link-check:
# TODO: replace kava in following line with project name
liche -r . --exclude "^http://127.*|^https://riot.im/app*|^http://kava-testnet*|^https://testnet-dex*|^https://kava3.data.kava.io*|^https://ipfs.io*|^https://apps.apple.com*|^https://kava.quicksync.io*"
-
-lint:
- golangci-lint run
- find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -d -s
- $(GO_BIN) mod verify
-.PHONY: lint
-
format:
find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -name '*.pb.go' | xargs gofmt -w -s
find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -name '*.pb.go' | xargs misspell -w
@@ -265,11 +268,11 @@ build-docker-local-0gchain:
# Run a 4-node testnet locally
localnet-start: build-linux localnet-stop
- @if ! [ -f build/node0/kvd/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/kvd:Z $(DOCKER_IMAGE_NAME)-node testnet --v 4 -o . --starting-ip-address 192.168.10.2 --keyring-backend=test ; fi
- docker-compose up -d
+ @if ! [ -f build/node0/kvd/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/kvd:Z kava/kavanode testnet --v 4 -o . --starting-ip-address 192.168.10.2 --keyring-backend=test ; fi
+ $(DOCKER) compose up -d
localnet-stop:
- docker-compose down
+ $(DOCKER) compose down
# Launch a new single validator chain
start:
@@ -311,12 +314,14 @@ test-basic: test
test-e2e: docker-build
$(GO_BIN) test -failfast -count=1 -v ./tests/e2e/...
+# run interchaintest tests (./tests/e2e-ibc)
+test-ibc: docker-build
+ cd tests/e2e-ibc && KAVA_TAG=local $(GO_BIN) test -timeout 10m .
+.PHONY: test-ibc
+
test:
@$(GO_BIN) test $$($(GO_BIN) list ./... | grep -v 'contrib' | grep -v 'tests/e2e')
-test-rocksdb:
- @go test -tags=rocksdb $(MAIN_ENTRY)/opendb
-
# Run cli integration tests
# `-p 4` to use 4 cores, `-tags cli_test` to tell $(GO_BIN) not to ignore the cli package
# These tests use the `kvd` or `kvcli` binaries in the build dir, or in `$BUILDDIR` if that env var is set.
@@ -327,6 +332,18 @@ test-cli: build
test-migrate:
@$(GO_BIN) test -v -count=1 ./migrate/...
+# Use the old Apple linker to workaround broken xcode - https://github.com/golang/go/issues/65169
+ifeq ($(OS_FAMILY),Darwin)
+ FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic
+endif
+
+test-fuzz:
+ @$(GO_BIN) test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzMintCoins ./x/precisebank/keeper
+ @$(GO_BIN) test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzBurnCoins ./x/precisebank/keeper
+ @$(GO_BIN) test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzSendCoins ./x/precisebank/keeper
+ @$(GO_BIN) test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzGenesisStateValidate_NonZeroRemainder ./x/precisebank/types
+ @$(GO_BIN) test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzGenesisStateValidate_ZeroRemainder ./x/precisebank/types
+
# Kick start lots of sims on an AWS cluster.
# This submits an AWS Batch job to run a lot of sims, each within a docker image. Results are uploaded to S3
start-remote-sims:
@@ -337,14 +354,14 @@ start-remote-sims:
# submit an array job on AWS Batch, using 1000 seeds, spot instances
aws batch submit-job \
-—job-name "master-$(VERSION)" \
- -—job-queue “simulation-1-queue-spot" \
+ -—job-queue "simulation-1-queue-spot" \
-—array-properties size=1000 \
-—job-definition $(BINARY_NAME)-sim-master \
-—container-override environment=[{SIM_NAME=master-$(VERSION)}]
update-kvtool:
git submodule init || true
- git submodule update
+ git submodule update --remote
cd tests/e2e/kvtool && make install
-.PHONY: all build-linux install clean build test test-cli test-all test-rest test-basic start-remote-sims
+.PHONY: all build-linux install build test test-cli test-all test-rest test-basic test-fuzz start-remote-sims
diff --git a/README.md b/README.md
index e24ba81a..50c7b940 100644
--- a/README.md
+++ b/README.md
@@ -17,13 +17,13 @@ Reference implementation of 0G Chain, the first modular AI chain. Built using th
+
+
+
+
+
+
+
+
+
+
+
Top
+
+## kava/kavadist/v1beta1/genesis.proto
+
+
+
+
+
+### GenesisState
+GenesisState defines the kavadist module's genesis state.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `params` | [Params](#kava.kavadist.v1beta1.Params) | | |
+| `previous_block_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Top
+
+## kava/kavadist/v1beta1/proposal.proto
+
+
+
+
+
+### CommunityPoolMultiSpendProposal
+CommunityPoolMultiSpendProposal spends from the community pool by sending to one or more
+addresses
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `title` | [string](#string) | | |
+| `description` | [string](#string) | | |
+| `recipient_list` | [MultiSpendRecipient](#kava.kavadist.v1beta1.MultiSpendRecipient) | repeated | |
+
+
+
+
+
+
+
+
+### CommunityPoolMultiSpendProposalJSON
+CommunityPoolMultiSpendProposalJSON defines a CommunityPoolMultiSpendProposal with a deposit
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `title` | [string](#string) | | |
+| `description` | [string](#string) | | |
+| `recipient_list` | [MultiSpendRecipient](#kava.kavadist.v1beta1.MultiSpendRecipient) | repeated | |
+| `deposit` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | |
+
+
+
+
+
+
+
+
+### MultiSpendRecipient
+MultiSpendRecipient defines a recipient and the amount of coins they are receiving
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `address` | [string](#string) | | |
+| `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Top
+
+## kava/kavadist/v1beta1/query.proto
+
+
+
+
+
+### QueryBalanceRequest
+QueryBalanceRequest defines the request type for querying x/kavadist balance.
+
+
+
+
+
+
+
+
+### QueryBalanceResponse
+QueryBalanceResponse defines the response type for querying x/kavadist balance.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `coins` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | |
+
+
+
+
+
+
+
+
+### QueryParamsRequest
+QueryParamsRequest defines the request type for querying x/kavadist parameters.
+
+
+
+
+
+
+
+
+### QueryParamsResponse
+QueryParamsResponse defines the response type for querying x/kavadist parameters.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `params` | [Params](#kava.kavadist.v1beta1.Params) | | |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Query
+Query defines the gRPC querier service.
+
+| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
+| ----------- | ------------ | ------------- | ------------| ------- | -------- |
+| `Params` | [QueryParamsRequest](#kava.kavadist.v1beta1.QueryParamsRequest) | [QueryParamsResponse](#kava.kavadist.v1beta1.QueryParamsResponse) | Params queries the parameters of x/kavadist module. | GET|/kava/kavadist/v1beta1/parameters|
+| `Balance` | [QueryBalanceRequest](#kava.kavadist.v1beta1.QueryBalanceRequest) | [QueryBalanceResponse](#kava.kavadist.v1beta1.QueryBalanceResponse) | Balance queries the balance of all coins of x/kavadist module. | GET|/kava/kavadist/v1beta1/balance|
+
+
+
+
+
+
+Top
+
+## kava/liquid/v1beta1/query.proto
+
+
+
+
+
+### QueryDelegatedBalanceRequest
+QueryDelegatedBalanceRequest defines the request type for Query/DelegatedBalance method.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `delegator` | [string](#string) | | delegator is the address of the account to query |
+
+
+
+
+
+
+
+
+### QueryDelegatedBalanceResponse
+DelegatedBalanceResponse defines the response type for the Query/DelegatedBalance method.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `vested` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | vested is the amount of all delegated coins that have vested (ie not locked) |
+| `vesting` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | vesting is the amount of all delegated coins that are still vesting (ie locked) |
+
+
+
+
+
+
+
+
+### QueryTotalSupplyRequest
+QueryTotalSupplyRequest defines the request type for Query/TotalSupply method.
+
+
+
+
+
+
+
+
+### QueryTotalSupplyResponse
+TotalSupplyResponse defines the response type for the Query/TotalSupply method.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `height` | [int64](#int64) | | Height is the block height at which these totals apply |
+| `result` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | Result is a list of coins supplied to liquid |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Query
+Query defines the gRPC querier service for liquid module
+
+| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
+| ----------- | ------------ | ------------- | ------------| ------- | -------- |
+| `DelegatedBalance` | [QueryDelegatedBalanceRequest](#kava.liquid.v1beta1.QueryDelegatedBalanceRequest) | [QueryDelegatedBalanceResponse](#kava.liquid.v1beta1.QueryDelegatedBalanceResponse) | DelegatedBalance returns an account's vesting and vested coins currently delegated to validators. It ignores coins in unbonding delegations. | GET|/kava/liquid/v1beta1/delegated_balance/{delegator}|
+| `TotalSupply` | [QueryTotalSupplyRequest](#kava.liquid.v1beta1.QueryTotalSupplyRequest) | [QueryTotalSupplyResponse](#kava.liquid.v1beta1.QueryTotalSupplyResponse) | TotalSupply returns the total sum of all coins currently locked into the liquid module. | GET|/kava/liquid/v1beta1/total_supply|
+
+
+
+
+
+
+Top
+
+## kava/liquid/v1beta1/tx.proto
+
+
+
+
+
+### MsgBurnDerivative
+MsgBurnDerivative defines the Msg/BurnDerivative request type.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `sender` | [string](#string) | | sender is the owner of the derivatives to be converted |
+| `validator` | [string](#string) | | validator is the validator of the derivatives to be converted |
+| `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | amount is the quantity of derivatives to be converted |
+
+
+
+
+
+
+
+
+### MsgBurnDerivativeResponse
+MsgBurnDerivativeResponse defines the Msg/BurnDerivative response type.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `received` | [string](#string) | | received is the number of delegation shares sent to the sender |
+
+
+
+
+
+
+
+
+### MsgMintDerivative
+MsgMintDerivative defines the Msg/MintDerivative request type.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `sender` | [string](#string) | | sender is the owner of the delegation to be converted |
+| `validator` | [string](#string) | | validator is the validator of the delegation to be converted |
+| `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | amount is the quantity of staked assets to be converted |
+
+
+
+
+
+
+
+
+### MsgMintDerivativeResponse
+MsgMintDerivativeResponse defines the Msg/MintDerivative response type.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `received` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | received is the amount of staking derivative minted and sent to the sender |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Msg
+Msg defines the liquid Msg service.
+
+| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
+| ----------- | ------------ | ------------- | ------------| ------- | -------- |
+| `MintDerivative` | [MsgMintDerivative](#kava.liquid.v1beta1.MsgMintDerivative) | [MsgMintDerivativeResponse](#kava.liquid.v1beta1.MsgMintDerivativeResponse) | MintDerivative defines a method for converting a delegation into staking deriviatives. | |
+| `BurnDerivative` | [MsgBurnDerivative](#kava.liquid.v1beta1.MsgBurnDerivative) | [MsgBurnDerivativeResponse](#kava.liquid.v1beta1.MsgBurnDerivativeResponse) | BurnDerivative defines a method for converting staking deriviatives into a delegation. | |
+
+
+
+
+
+
+Top
+
+## kava/precisebank/v1/genesis.proto
+
+
+
+
+
+### FractionalBalance
+FractionalBalance defines the fractional portion of an account balance
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `address` | [string](#string) | | address is the address of the balance holder. |
+| `amount` | [string](#string) | | amount indicates amount of only the fractional balance owned by the address. FractionalBalance currently only supports tracking 1 single asset, e.g. fractional balances of ukava. |
+
+
+
+
+
+
+
+
+### GenesisState
+GenesisState defines the precisebank module's genesis state.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `balances` | [FractionalBalance](#kava.precisebank.v1.FractionalBalance) | repeated | balances is a list of all the balances in the precisebank module. |
+| `remainder` | [string](#string) | | remainder is an internal value of how much extra fractional digits are still backed by the reserve, but not assigned to any account. |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Top
+
+## kava/precisebank/v1/query.proto
+
+
+
+
+
+### QueryFractionalBalanceRequest
+QueryFractionalBalanceRequest defines the request type for Query/FractionalBalance method.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `address` | [string](#string) | | address is the account address to query fractional balance for. |
+
+
+
+
+
+
+
+
+### QueryFractionalBalanceResponse
+QueryFractionalBalanceResponse defines the response type for Query/FractionalBalance method.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `fractional_balance` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | fractional_balance is the fractional balance of the address. |
+
+
+
+
+
+
+
+
+### QueryRemainderRequest
+QueryRemainderRequest defines the request type for Query/Remainder method.
+
+
+
+
+
+
+
+
+### QueryRemainderResponse
+QueryRemainderResponse defines the response type for Query/Remainder method.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `remainder` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | remainder is the amount backed by the reserve, but not yet owned by any account, i.e. not in circulation. |
+
+
+
+
+
+
+
+
+### QueryTotalFractionalBalancesRequest
+QueryTotalFractionalBalancesRequest defines the request type for Query/TotalFractionalBalances method.
+
+
+
+
+
+
+
+
+### QueryTotalFractionalBalancesResponse
+QueryTotalFractionalBalancesResponse defines the response type for Query/TotalFractionalBalances method.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `total` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | total is the total sum of all fractional balances managed by the precisebank module. |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Query
+Query defines the gRPC querier service for precisebank module
+
+| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
+| ----------- | ------------ | ------------- | ------------| ------- | -------- |
+| `TotalFractionalBalances` | [QueryTotalFractionalBalancesRequest](#kava.precisebank.v1.QueryTotalFractionalBalancesRequest) | [QueryTotalFractionalBalancesResponse](#kava.precisebank.v1.QueryTotalFractionalBalancesResponse) | TotalFractionalBalances returns the total sum of all fractional balances managed by the precisebank module. | GET|/kava/precisebank/v1/total_fractional_balances|
+| `Remainder` | [QueryRemainderRequest](#kava.precisebank.v1.QueryRemainderRequest) | [QueryRemainderResponse](#kava.precisebank.v1.QueryRemainderResponse) | Remainder returns the amount backed by the reserve, but not yet owned by any account, i.e. not in circulation. | GET|/kava/precisebank/v1/remainder|
+| `FractionalBalance` | [QueryFractionalBalanceRequest](#kava.precisebank.v1.QueryFractionalBalanceRequest) | [QueryFractionalBalanceResponse](#kava.precisebank.v1.QueryFractionalBalanceResponse) | FractionalBalance returns only the fractional balance of an address. This does not include any integer balance. | GET|/kava/precisebank/v1/fractional_balance/{address}|
+
+
+
+
+
+
+Top
+
+## kava/pricefeed/v1beta1/store.proto
+
+
+
+
### CurrentPrice
CurrentPrice defines a current price for a particular market in the pricefeed
diff --git a/go.mod b/go.mod
index ecf7edc7..32a73f24 100644
--- a/go.mod
+++ b/go.mod
@@ -4,61 +4,62 @@ go 1.21
require (
cosmossdk.io/errors v1.0.1
+ cosmossdk.io/log v1.3.1
cosmossdk.io/math v1.3.0
cosmossdk.io/simapp v0.0.0-20231127212628-044ff4d8c015
+ github.com/Kava-Labs/opendb v0.0.0-20240719173129-a2f11f6d7e51
github.com/cenkalti/backoff/v4 v4.1.3
- github.com/cometbft/cometbft v0.37.4
+ github.com/cometbft/cometbft v0.37.9
github.com/cometbft/cometbft-db v0.9.1
github.com/coniks-sys/coniks-go v0.0.0-20180722014011-11acf4819b71
github.com/consensys/gnark-crypto v0.12.1
- github.com/cosmos/cosmos-proto v1.0.0-beta.4
+ github.com/cosmos/cosmos-db v1.0.2
+ github.com/cosmos/cosmos-proto v1.0.0-beta.5
github.com/cosmos/cosmos-sdk v0.47.10
github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/gogoproto v1.4.10
- github.com/cosmos/iavl v0.20.1
+ github.com/cosmos/iavl v1.2.0
github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3
github.com/cosmos/ibc-go/modules/light-clients/08-wasm v0.1.1-ibc-go-v7.3-wasmvm-v1.5
github.com/cosmos/ibc-go/v7 v7.4.0
github.com/ethereum/go-ethereum v1.10.26
github.com/evmos/ethermint v0.21.0
- github.com/go-kit/kit v0.12.0
github.com/gogo/protobuf v1.3.2
- github.com/golang/protobuf v1.5.3
+ github.com/golang/protobuf v1.5.4
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
- github.com/linxGnu/grocksdb v1.8.6
+ github.com/linxGnu/grocksdb v1.8.13
github.com/pelletier/go-toml/v2 v2.1.0
- github.com/prometheus/client_golang v1.14.0
github.com/shopspring/decimal v1.4.0
github.com/spf13/cast v1.6.0
- github.com/spf13/cobra v1.7.0
- github.com/spf13/viper v1.16.0
- github.com/stretchr/testify v1.8.4
+ github.com/spf13/cobra v1.8.0
+ github.com/spf13/viper v1.18.2
+ github.com/stretchr/testify v1.9.0
github.com/subosito/gotenv v1.6.0
golang.org/x/crypto v0.24.0
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
- google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0
- google.golang.org/grpc v1.60.1
- google.golang.org/protobuf v1.32.0
+ google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de
+ google.golang.org/grpc v1.63.2
+ google.golang.org/protobuf v1.33.0
sigs.k8s.io/yaml v1.4.0
)
require (
- cloud.google.com/go v0.111.0 // indirect
- cloud.google.com/go/compute v1.23.3 // indirect
+ cloud.google.com/go v0.112.0 // indirect
+ cloud.google.com/go/compute v1.24.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
- cloud.google.com/go/iam v1.1.5 // indirect
- cloud.google.com/go/storage v1.35.1 // indirect
+ cloud.google.com/go/iam v1.1.6 // indirect
+ cloud.google.com/go/storage v1.36.0 // indirect
cosmossdk.io/api v0.3.1 // indirect
cosmossdk.io/core v0.6.1 // indirect
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
- cosmossdk.io/log v1.3.1 // indirect
cosmossdk.io/tools/rosetta v0.2.1 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect
github.com/CosmWasm/wasmvm v1.5.2 // indirect
+ github.com/DataDog/zstd v1.5.5 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/VictoriaMetrics/fastcache v1.6.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
@@ -67,30 +68,32 @@ require (
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/bits-and-blooms/bitset v1.7.0 // indirect
- github.com/btcsuite/btcd v0.23.4 // indirect
+ github.com/btcsuite/btcd v0.24.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
- github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
- github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
+ github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
+ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
- github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
- github.com/cockroachdb/errors v1.10.0 // indirect
+ github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
+ github.com/cockroachdb/pebble v1.1.0 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
+ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect
github.com/confio/ics23/go v0.9.0 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/ics23/go v0.10.0 // indirect
- github.com/cosmos/ledger-cosmos-go v0.13.1 // indirect
+ github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect
github.com/creachadair/taskgroup v0.4.2 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
- github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
@@ -100,20 +103,22 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
- github.com/felixge/httpsnoop v1.0.2 // indirect
+ github.com/emicklei/dot v1.6.1 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect
- github.com/getsentry/sentry-go v0.23.0 // indirect
+ github.com/getsentry/sentry-go v0.27.0 // indirect
+ github.com/go-kit/kit v0.13.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
- github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
- github.com/golang/glog v1.1.2 // indirect
+ github.com/golang/glog v1.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
@@ -121,7 +126,7 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/google/s2a-go v0.1.7 // indirect
- github.com/google/uuid v1.4.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
@@ -131,11 +136,12 @@ require (
github.com/gtank/merlin v0.1.1 // indirect
github.com/gtank/ristretto255 v0.1.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
- github.com/hashicorp/go-getter v1.7.1 // indirect
+ github.com/hashicorp/go-getter v1.7.5 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
- github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
+ github.com/hashicorp/golang-lru v1.0.2 // indirect
+ github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
@@ -148,11 +154,10 @@ require (
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
- github.com/klauspost/compress v1.17.0 // indirect
+ github.com/klauspost/compress v1.17.7 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.7 // indirect
- github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
@@ -167,28 +172,33 @@ require (
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
- github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect
+ github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
+ github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
- github.com/prometheus/client_model v0.3.0 // indirect
+ github.com/prometheus/client_golang v1.14.0 // indirect
+ github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.42.0 // indirect
- github.com/prometheus/procfs v0.9.0 // indirect
+ github.com/prometheus/procfs v0.13.0 // indirect
github.com/prometheus/tsdb v0.7.1 // indirect
github.com/rakyll/statik v0.1.7 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rjeczalik/notify v0.9.1 // indirect
- github.com/rogpeppe/go-internal v1.11.0 // indirect
+ github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/cors v1.8.3 // indirect
github.com/rs/zerolog v1.32.0 // indirect
+ github.com/sagikazarmark/locafero v0.4.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
- github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/status-im/keycard-go v0.2.0 // indirect
+ github.com/stretchr/objx v0.5.2 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
- github.com/tidwall/btree v1.6.0 // indirect
+ github.com/tidwall/btree v1.7.0 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
@@ -197,20 +207,24 @@ require (
github.com/zondax/ledger-go v0.14.3 // indirect
go.etcd.io/bbolt v1.3.8 // indirect
go.opencensus.io v0.24.0 // indirect
- go.opentelemetry.io/otel v1.19.0 // indirect
- go.opentelemetry.io/otel/metric v1.19.0 // indirect
- go.opentelemetry.io/otel/trace v1.19.0 // indirect
- golang.org/x/net v0.21.0 // indirect
- golang.org/x/oauth2 v0.15.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
+ go.opentelemetry.io/otel v1.22.0 // indirect
+ go.opentelemetry.io/otel/metric v1.22.0 // indirect
+ go.opentelemetry.io/otel/trace v1.22.0 // indirect
+ go.uber.org/atomic v1.10.0 // indirect
+ go.uber.org/multierr v1.9.0 // indirect
+ golang.org/x/net v0.24.0 // indirect
+ golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
- google.golang.org/api v0.153.0 // indirect
+ google.golang.org/api v0.162.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
- google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
+ google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
@@ -224,20 +238,23 @@ replace (
// Use the cosmos keyring code
github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0
// Use cometbft fork of tendermint
- github.com/cometbft/cometbft => github.com/kava-labs/cometbft v0.37.4-kava.1
- github.com/cometbft/cometbft-db => github.com/kava-labs/cometbft-db v0.9.1-kava.1
+ github.com/cometbft/cometbft => github.com/kava-labs/cometbft v0.37.9-kava.1
+ github.com/cometbft/cometbft-db => github.com/kava-labs/cometbft-db v0.9.1-kava.2
// Use cosmos-sdk fork with backported fix for unsafe-reset-all, staking transfer events, and custom tally handler support
// github.com/cosmos/cosmos-sdk => github.com/0glabs/cosmos-sdk v0.46.11-kava.3
- github.com/cosmos/cosmos-sdk => github.com/0glabs/cosmos-sdk v0.47.10-0glabs.3
+ github.com/cosmos/cosmos-sdk => github.com/0glabs/cosmos-sdk v0.47.10-0glabs.5
+ github.com/cosmos/iavl => github.com/kava-labs/iavl v1.2.0-kava.1
// See https://github.com/cosmos/cosmos-sdk/pull/13093
github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt/v4 v4.4.2
- // Use go-ethereum fork with precompiles
+ // Tracking kava-labs/go-ethereum kava/release/v1.10 branch
+ // TODO: Tag before release
github.com/ethereum/go-ethereum => github.com/evmos/go-ethereum v1.10.26-evmos-rc2
// Use ethermint fork that respects min-gas-price with NoBaseFee true and london enabled, and includes eip712 support
- github.com/evmos/ethermint => github.com/0glabs/ethermint v0.21.0-0g.v3.0.3
+ github.com/evmos/ethermint => github.com/0glabs/ethermint v0.21.0-0g.v3.1.2
// See https://github.com/cosmos/cosmos-sdk/pull/10401, https://github.com/cosmos/cosmos-sdk/commit/0592ba6158cd0bf49d894be1cef4faeec59e8320
github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.0
// Downgraded to avoid bugs in following commits which causes "version does not exist" errors
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
+ // Avoid change in slices.SortFunc, see https://github.com/cosmos/cosmos-sdk/issues/20159
golang.org/x/exp => golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb
)
diff --git a/go.sum b/go.sum
index 2120af9b..36dea632 100644
--- a/go.sum
+++ b/go.sum
@@ -32,8 +32,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
-cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
-cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
+cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
+cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4=
cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=
cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=
cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
@@ -71,8 +71,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
-cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
-cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
+cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
+cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
@@ -112,8 +112,8 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97
cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=
-cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
-cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
+cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
+cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
@@ -174,8 +174,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
-cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
-cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
+cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
+cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=
cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
@@ -209,10 +209,10 @@ filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw=
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA=
-github.com/0glabs/cosmos-sdk v0.47.10-0glabs.3 h1:Wx3tVMTuFaaHDeJT/OzT7QLfAIpeaZsG9R6XoTOyKCw=
-github.com/0glabs/cosmos-sdk v0.47.10-0glabs.3/go.mod h1:BWo24B8cApWcO2/widWYIdt3CPxbh+HCSypCPpjTjog=
-github.com/0glabs/ethermint v0.21.0-0g.v3.0.3 h1:QNrXBQV5L/9FvYRUzJRXMV745xBmJhIP0aEdo0u8x+8=
-github.com/0glabs/ethermint v0.21.0-0g.v3.0.3/go.mod h1:HYQUhvcZBIG71H3xlxQSk0XyQEjeaHsduOj6O2QImrE=
+github.com/0glabs/cosmos-sdk v0.47.10-0glabs.5 h1:CwL3i7ccMjOXpKnJ9w0WkM7UpTWLXa/W2ZAwpxFUfb8=
+github.com/0glabs/cosmos-sdk v0.47.10-0glabs.5/go.mod h1:KskIVnhXTFqrw7CDccMvx7To5KzUsOomIsQV7sPGOog=
+github.com/0glabs/ethermint v0.21.0-0g.v3.1.2 h1:qSTk1t5u3ppN/00bBaZYBF4+GiFlyr+92UlcWW2J8Zg=
+github.com/0glabs/ethermint v0.21.0-0g.v3.1.2/go.mod h1:MjUzm+q5TiZIBNVq29B3a5Lx0A946QXBBvSSqD4l8ks=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=
@@ -229,9 +229,13 @@ github.com/CosmWasm/wasmvm v1.5.2/go.mod h1:Q0bSEtlktzh7W2hhEaifrFp1Erx11ckQZmjq
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
+github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
+github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
+github.com/Kava-Labs/opendb v0.0.0-20240719173129-a2f11f6d7e51 h1:tMTENCeSPIJO8yCpEQbT15XYXt4EFNQUx3s334uxVts=
+github.com/Kava-Labs/opendb v0.0.0-20240719173129-a2f11f6d7e51/go.mod h1:LbPsJiWvj90NT3Y9YV8EFPkWfvp8A15Tp88qqKa3LxA=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
-github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
-github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
+github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
+github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
@@ -303,9 +307,9 @@ github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13P
github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y=
-github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
-github.com/btcsuite/btcd v0.23.4 h1:IzV6qqkfwbItOS/sg/aDfPDsjPP8twrCOE2R93hxMlQ=
-github.com/btcsuite/btcd v0.23.4/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
+github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
+github.com/btcsuite/btcd v0.24.0 h1:gL3uHE/IaFj6fcZSu03SvqPMSx7s/dPzfpG/atRwWdo=
+github.com/btcsuite/btcd v0.24.0/go.mod h1:K4IDc1593s8jKXIF7yS7yCTSxrknB9z0STzc2j6XgE4=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
@@ -313,11 +317,12 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
-github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
-github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0=
+github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
+github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
-github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@@ -349,8 +354,8 @@ github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
-github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
@@ -378,15 +383,23 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
+github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/cockroachdb/errors v1.10.0 h1:lfxS8zZz1+OjtV4MtNWgboi/W5tyLEB6VQZBXN+0VUU=
-github.com/cockroachdb/errors v1.10.0/go.mod h1:lknhIsEVQ9Ss/qKDBQS/UqFSvPQjOwNq2qyKAxtHRqE=
+github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
+github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
+github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
+github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
+github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
+github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
+github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
+github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI=
github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA=
@@ -413,8 +426,10 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk=
github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis=
-github.com/cosmos/cosmos-proto v1.0.0-beta.4 h1:aEL7tU/rLOmxZQ9z4i7mzxcLbSCY48OdY7lIWTLG7oU=
-github.com/cosmos/cosmos-proto v1.0.0-beta.4/go.mod h1:oeB+FyVzG3XrQJbJng0EnV8Vljfk9XvTIpGILNU/9Co=
+github.com/cosmos/cosmos-db v1.0.2 h1:hwMjozuY1OlJs/uh6vddqnk9j7VamLv+0DBlbEXbAKs=
+github.com/cosmos/cosmos-db v1.0.2/go.mod h1:Z8IXcFJ9PqKK6BIsVOB3QXtkKoqUOp1vRvPT39kOXEA=
+github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
+github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
@@ -423,8 +438,6 @@ github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ
github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU=
github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI=
github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek=
-github.com/cosmos/iavl v0.20.1 h1:rM1kqeG3/HBT85vsZdoSNsehciqUQPWrR4BYmqE2+zg=
-github.com/cosmos/iavl v0.20.1/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A=
github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3 h1:MZGDMETv72suFpTAD6VPGqSIm1FJcChtk2HmVh9D+Bo=
github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3/go.mod h1:UvDmcGIWJPIytq+Q78/ff5NTOsuX/7IrNgEugTW5i0s=
github.com/cosmos/ibc-go/modules/light-clients/08-wasm v0.1.1-ibc-go-v7.3-wasmvm-v1.5 h1:sMoHjep+KInjMrppNCEutMVm1p8nI9WhKCuMQ+EcUHw=
@@ -435,15 +448,16 @@ github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZD
github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0=
github.com/cosmos/keyring v1.2.0 h1:8C1lBP9xhImmIabyXW4c3vFjjLiBdGCmfLUfeZlV1Yo=
github.com/cosmos/keyring v1.2.0/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA=
-github.com/cosmos/ledger-cosmos-go v0.13.1 h1:12ac9+GwBb9BjP7X5ygpFk09Itwzjzfmg6A2CWFjoVs=
-github.com/cosmos/ledger-cosmos-go v0.13.1/go.mod h1:5tv2RVJEd2+Y38TIQN4CRjJeQGyqOEiKJDfqhk5UjqE=
+github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM=
+github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8=
github.com/cosmos/rosetta-sdk-go v0.10.0 h1:E5RhTruuoA7KTIXUcMicL76cffyeoyvNybzUGSKFTcM=
github.com/cosmos/rosetta-sdk-go v0.10.0/go.mod h1:SImAZkb96YbwvoRkzSMQB6noNJXFgWl/ENIznEoYQI4=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8=
github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
@@ -459,11 +473,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
-github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
+github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
@@ -504,6 +519,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/emicklei/dot v1.6.1 h1:ujpDlBkkwgWUY+qPId5IwapRW/xEoligRSYjioR6DFI=
+github.com/emicklei/dot v1.6.1/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -515,13 +532,15 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
+github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/evmos/go-ethereum v1.10.26-evmos-rc2 h1:tYghk1ZZ8X4/OQ4YI9hvtm8aSN8OSqO0g9vo/sCMdBo=
github.com/evmos/go-ethereum v1.10.26-evmos-rc2/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
-github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
@@ -541,8 +560,8 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqG
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
-github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE=
-github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
+github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
+github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
@@ -556,8 +575,8 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
-github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4=
-github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
+github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
+github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -566,8 +585,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
-github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
@@ -620,8 +639,8 @@ github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
-github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
+github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
+github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -656,8 +675,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
@@ -723,8 +742,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
-github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
@@ -780,8 +799,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
-github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY=
-github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744=
+github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4=
+github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
@@ -802,8 +821,11 @@ github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
+github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
@@ -880,10 +902,12 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0=
github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
-github.com/kava-labs/cometbft v0.37.4-kava.1 h1:QRuyBieWdUBpe4pcXgzu1SdMH2lkTaqXr/JPIeqdiHE=
-github.com/kava-labs/cometbft v0.37.4-kava.1/go.mod h1:Cmg5Hp4sNpapm7j+x0xRyt2g0juQfmB752ous+pA0G8=
-github.com/kava-labs/cometbft-db v0.9.1-kava.1 h1:0KmSPdXYdRp6TsgKuMxRnMZCMEGC5ysIVjuJddYr4tw=
-github.com/kava-labs/cometbft-db v0.9.1-kava.1/go.mod h1:iliyWaoV0mRwBJoizElCwwRA9Tf7jZJOURcRZF9m60U=
+github.com/kava-labs/cometbft v0.37.9-kava.1 h1:0mMsAhpV8p0peD9sabIZ//M4nP6LiiZ0o34gfuHdANY=
+github.com/kava-labs/cometbft v0.37.9-kava.1/go.mod h1:j0Q3RqrCd+cztWCugs3obbzC4NyHGBPZZjtm/fWV00I=
+github.com/kava-labs/cometbft-db v0.9.1-kava.2 h1:ZQaio886ifvml9XtJB4IYHhlArgA3+/a5Zwidg7H2J8=
+github.com/kava-labs/cometbft-db v0.9.1-kava.2/go.mod h1:PvUZbx7zeR7I4CAvtKBoii/5ia5gXskKjDjIVpt7gDw=
+github.com/kava-labs/iavl v1.2.0-kava.1 h1:HPme3nVrR25XshEFDckMg6fp0tVfpAjTi32/5Iiyuzk=
+github.com/kava-labs/iavl v1.2.0-kava.1/go.mod h1:HidWWLVAtODJqFD6Hbne2Y0q3SdxByJepHUOeoH4LiI=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -894,8 +918,8 @@ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
-github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
-github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
+github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
@@ -925,12 +949,10 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
-github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/linxGnu/grocksdb v1.8.6 h1:O7I6SIGPrypf3f/gmrrLUBQDKfO8uOoYdWf4gLS06tc=
-github.com/linxGnu/grocksdb v1.8.6/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY=
+github.com/linxGnu/grocksdb v1.8.13 h1:X3Id7Obhf8qLY9WPc4LmmtIyabmdDf810XSFDnLlW7E=
+github.com/linxGnu/grocksdb v1.8.13/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -1067,6 +1089,8 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@@ -1077,8 +1101,8 @@ github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9
github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
-github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU=
-github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
+github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 h1:jik8PHtAIsPlCRJjJzl4udgEf7hawInF9texMeO2jrU=
+github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -1109,8 +1133,8 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
-github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@@ -1127,8 +1151,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
-github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
+github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
@@ -1145,8 +1169,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
-github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo=
github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
@@ -1159,6 +1183,10 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
@@ -1182,6 +1210,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -1193,18 +1223,16 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
-github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
-github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
-github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
-github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
-github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg=
@@ -1214,8 +1242,9 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -1227,8 +1256,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
@@ -1236,8 +1266,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
-github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
-github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
+github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
+github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
@@ -1308,22 +1338,30 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
-go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
-go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
-go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
-go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
-go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
-go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
-go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
-go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
+go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
+go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
+go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
+go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
+go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
+go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
+go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
+go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
+go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU=
go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
+go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@@ -1452,8 +1490,8 @@ golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfS
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1479,8 +1517,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
-golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
-golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
+golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
+golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1765,8 +1803,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ
google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
-google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4=
-google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
+google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps=
+google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1885,12 +1923,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw
google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
-google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg=
-google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0=
-google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
-google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
+google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
+google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
+google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@@ -1932,8 +1970,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
-google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
-google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
+google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
+google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1950,8 +1988,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
-google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/golangci.yml b/golangci.yml
deleted file mode 100644
index 371e83fa..00000000
--- a/golangci.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-run:
- tests: false
-# # timeout for analysis, e.g. 30s, 5m, default is 1m
-# timeout: 5m
-
-linters:
- disable-all: true
- enable:
- - bodyclose
- - deadcode
- - depguard
- - dogsled
- # - errcheck
- - goconst
- - gocritic
- - gofmt
- - goimports
- - golint
- - gosec
- - gosimple
- - govet
- - ineffassign
- - interfacer
- - maligned
- - misspell
- - nakedret
- - prealloc
- - scopelint
- - staticcheck
- - structcheck
- - stylecheck
- - typecheck
- - unconvert
- - unused
- - misspell
- - wsl
-
-issues:
- exclude-rules:
- - text: "Use of weak random number generator"
- linters:
- - gosec
- - text: "comment on exported var"
- linters:
- - golint
-
-linters-settings:
- dogsled:
- max-blank-identifiers: 3
- maligned:
- # print struct with more effective memory layout or not, false by default
- suggest-new: true
diff --git a/proto/zgc/precisebank/v1/genesis.proto b/proto/zgc/precisebank/v1/genesis.proto
new file mode 100644
index 00000000..7a87416e
--- /dev/null
+++ b/proto/zgc/precisebank/v1/genesis.proto
@@ -0,0 +1,42 @@
+syntax = "proto3";
+package zgc.precisebank.v1;
+
+import "cosmos_proto/cosmos.proto";
+import "gogoproto/gogo.proto";
+
+option go_package = "github.com/0glabs/0g-chain/x/precisebank/types";
+
+// GenesisState defines the precisebank module's genesis state.
+message GenesisState {
+ // balances is a list of all the balances in the precisebank module.
+ repeated FractionalBalance balances = 1 [
+ (gogoproto.castrepeated) = "FractionalBalances",
+ (gogoproto.nullable) = false
+ ];
+
+ // remainder is an internal value of how much extra fractional digits are
+ // still backed by the reserve, but not assigned to any account.
+ string remainder = 2 [
+ (cosmos_proto.scalar) = "cosmos.Int",
+ (gogoproto.customtype) = "cosmossdk.io/math.Int",
+ (gogoproto.nullable) = false
+ ];
+}
+
+// FractionalBalance defines the fractional portion of an account balance
+message FractionalBalance {
+ option (gogoproto.equal) = false;
+ option (gogoproto.goproto_getters) = false;
+
+ // address is the address of the balance holder.
+ string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
+
+ // amount indicates amount of only the fractional balance owned by the
+ // address. FractionalBalance currently only supports tracking 1 single asset,
+ // e.g. fractional balances of ukava.
+ string amount = 2 [
+ (cosmos_proto.scalar) = "cosmos.Int",
+ (gogoproto.customtype) = "cosmossdk.io/math.Int",
+ (gogoproto.nullable) = false
+ ];
+}
diff --git a/proto/zgc/precisebank/v1/query.proto b/proto/zgc/precisebank/v1/query.proto
new file mode 100644
index 00000000..2561d622
--- /dev/null
+++ b/proto/zgc/precisebank/v1/query.proto
@@ -0,0 +1,62 @@
+syntax = "proto3";
+package zgc.precisebank.v1;
+
+import "cosmos/base/v1beta1/coin.proto";
+import "gogoproto/gogo.proto";
+import "google/api/annotations.proto";
+
+option go_package = "github.com/0glabs/0g-chain/x/precisebank/types";
+option (gogoproto.goproto_getters_all) = false;
+
+// Query defines the gRPC querier service for precisebank module
+service Query {
+ // TotalFractionalBalances returns the total sum of all fractional balances
+ // managed by the precisebank module.
+ rpc TotalFractionalBalances(QueryTotalFractionalBalancesRequest) returns (QueryTotalFractionalBalancesResponse) {
+ option (google.api.http).get = "/0g/precisebank/v1/total_fractional_balances";
+ }
+
+ // Remainder returns the amount backed by the reserve, but not yet owned by
+ // any account, i.e. not in circulation.
+ rpc Remainder(QueryRemainderRequest) returns (QueryRemainderResponse) {
+ option (google.api.http).get = "/0g/precisebank/v1/remainder";
+ }
+
+ // FractionalBalance returns only the fractional balance of an address. This
+ // does not include any integer balance.
+ rpc FractionalBalance(QueryFractionalBalanceRequest) returns (QueryFractionalBalanceResponse) {
+ option (google.api.http).get = "/0g/precisebank/v1/fractional_balance/{address}";
+ }
+}
+
+// QueryTotalFractionalBalancesRequest defines the request type for Query/TotalFractionalBalances method.
+message QueryTotalFractionalBalancesRequest {}
+
+// QueryTotalFractionalBalancesResponse defines the response type for Query/TotalFractionalBalances method.
+message QueryTotalFractionalBalancesResponse {
+ // total is the total sum of all fractional balances managed by the precisebank
+ // module.
+ cosmos.base.v1beta1.Coin total = 1 [(gogoproto.nullable) = false];
+}
+
+// QueryRemainderRequest defines the request type for Query/Remainder method.
+message QueryRemainderRequest {}
+
+// QueryRemainderResponse defines the response type for Query/Remainder method.
+message QueryRemainderResponse {
+ // remainder is the amount backed by the reserve, but not yet owned by any
+ // account, i.e. not in circulation.
+ cosmos.base.v1beta1.Coin remainder = 1 [(gogoproto.nullable) = false];
+}
+
+// QueryFractionalBalanceRequest defines the request type for Query/FractionalBalance method.
+message QueryFractionalBalanceRequest {
+ // address is the account address to query fractional balance for.
+ string address = 1;
+}
+
+// QueryFractionalBalanceResponse defines the response type for Query/FractionalBalance method.
+message QueryFractionalBalanceResponse {
+ // fractional_balance is the fractional balance of the address.
+ cosmos.base.v1beta1.Coin fractional_balance = 1 [(gogoproto.nullable) = false];
+}
diff --git a/tests/e2e-ibc/chainconfig.go b/tests/e2e-ibc/chainconfig.go
new file mode 100644
index 00000000..b8a2a8a9
--- /dev/null
+++ b/tests/e2e-ibc/chainconfig.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "os"
+
+ "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos"
+ "github.com/strangelove-ventures/interchaintest/v7/ibc"
+ "github.com/strangelove-ventures/interchaintest/v7/testutil"
+)
+
+const (
+ KavaTestChainId = "kava_8888-1"
+ KavaEvmTestChainId = int64(8888)
+)
+
+func DefaultKavaChainConfig(chainId string) ibc.ChainConfig {
+ kavaImageTag := os.Getenv("KAVA_TAG")
+ if kavaImageTag == "" {
+ kavaImageTag = "v0.26.0-rocksdb"
+ }
+
+ // app.toml overrides
+ jsonRpcOverrides := make(testutil.Toml)
+ jsonRpcOverrides["address"] = "0.0.0.0:8545"
+ appTomlOverrides := make(testutil.Toml)
+ appTomlOverrides["json-rpc"] = jsonRpcOverrides
+
+ // genesis param overrides
+ genesis := []cosmos.GenesisKV{
+ cosmos.NewGenesisKV("consensus_params.block.max_gas", "20000000"),
+ cosmos.NewGenesisKV("app_state.evm.params.evm_denom", "akava"),
+ // update for fast voting periods
+ cosmos.NewGenesisKV("app_state.gov.params.voting_period", "10s"),
+ cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.denom", "ukava"),
+ }
+
+ return ibc.ChainConfig{
+ Type: "cosmos",
+ ChainID: chainId,
+ Images: []ibc.DockerImage{{Repository: "kava/kava", Version: kavaImageTag, UidGid: "0:0"}},
+ // Images: []ibc.DockerImage{{Repository: "ghcr.io/strangelove-ventures/heighliner/kava", Version: "v0.26.0", UidGid: "1025:1025"}},
+ Bin: "kava",
+ Bech32Prefix: "kava",
+ Denom: "ukava",
+ GasPrices: "0ukava", // 0 gas price makes calculating expected balances simpler
+ GasAdjustment: 1.5,
+ TrustingPeriod: "168h0m0s",
+ ModifyGenesis: cosmos.ModifyGenesis(genesis),
+ ExposeAdditionalPorts: []string{"8545/tcp"},
+ ConfigFileOverrides: map[string]any{"config/app.toml": appTomlOverrides},
+ }
+}
diff --git a/tests/e2e-ibc/conformance_test.go b/tests/e2e-ibc/conformance_test.go
new file mode 100644
index 00000000..a3c6ccd9
--- /dev/null
+++ b/tests/e2e-ibc/conformance_test.go
@@ -0,0 +1,59 @@
+package main_test
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "go.uber.org/zap/zaptest"
+
+ "github.com/strangelove-ventures/interchaintest/v7"
+ "github.com/strangelove-ventures/interchaintest/v7/conformance"
+ "github.com/strangelove-ventures/interchaintest/v7/ibc"
+ "github.com/strangelove-ventures/interchaintest/v7/testreporter"
+ "github.com/stretchr/testify/require"
+
+ kavainterchain "github.com/kava-labs/kava/tests/interchain"
+)
+
+var (
+ numFullNodes = 0
+ numValidators = 1
+)
+
+func TestIbcConformance(t *testing.T) {
+ // t.Skip("skipping conformance test")
+
+ ctx := context.Background()
+
+ // setup chain factories: must be exactly two chains to run conformance between
+ cfs := make([]interchaintest.ChainFactory, 0)
+ cfs = append(cfs,
+ interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{
+ {
+ Name: "kava",
+ ChainConfig: kavainterchain.DefaultKavaChainConfig(kavainterchain.KavaTestChainId),
+ // override default number of nodes to limit the number of ports we need to bind to
+ // running conformance tests without these limits result in errors that look like
+ // tendermint rpc client status: post failed: Post "http://127.0.0.1:": dial tcp 127.0.0.1:: connect: can't assign requested address
+ NumValidators: &numValidators,
+ NumFullNodes: &numFullNodes,
+ },
+ {Name: "osmosis", Version: "v24.0.1", NumValidators: &numValidators, NumFullNodes: &numFullNodes},
+ }),
+ )
+
+ // setup relayer factory
+ rfs := make([]interchaintest.RelayerFactory, 0)
+ rfs = append(rfs,
+ interchaintest.NewBuiltinRelayerFactory(ibc.CosmosRly, zaptest.NewLogger(t)),
+ )
+
+ // test reporter
+ f, err := interchaintest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix()))
+ require.NoError(t, err)
+ rep := testreporter.NewReporter(f)
+
+ conformance.Test(t, ctx, cfs, rfs, rep)
+}
diff --git a/tests/e2e-ibc/erc20_test.go b/tests/e2e-ibc/erc20_test.go
new file mode 100644
index 00000000..50eadf8d
--- /dev/null
+++ b/tests/e2e-ibc/erc20_test.go
@@ -0,0 +1,290 @@
+package main_test
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "math/big"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+ "go.uber.org/zap/zaptest"
+
+ "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ gov1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
+ paramsutils "github.com/cosmos/cosmos-sdk/x/params/client/utils"
+ transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
+
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/strangelove-ventures/interchaintest/v7"
+ "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos"
+ "github.com/strangelove-ventures/interchaintest/v7/ibc"
+ "github.com/strangelove-ventures/interchaintest/v7/testreporter"
+
+ "github.com/0glabs/0g-chain/app"
+ "github.com/0glabs/0g-chain/client/erc20"
+ "github.com/0glabs/0g-chain/tests/e2e/runner"
+ "github.com/0glabs/0g-chain/tests/e2e/testutil"
+ kavainterchain "github.com/0glabs/0g-chain/tests/interchain"
+ "github.com/0glabs/0g-chain/tests/util"
+ evmutiltypes "github.com/0glabs/0g-chain/x/evmutil/types"
+)
+
+// This test does the following:
+// - set up a network with kava & cosmoshub w/ IBC channels between them
+// - deploy an ERC20 to Kava EVM
+// - configure Kava to support conversion of ERC20 to Cosmos Coin
+// - IBC the sdk.Coin representation of the ERC20 to cosmoshub
+func TestInterchainErc20(t *testing.T) {
+ app.SetSDKConfig()
+ ctx := context.Background()
+
+ // Configure & run chains with interchaintest
+ cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{
+ {Name: "kava", ChainConfig: kavainterchain.DefaultKavaChainConfig(kavainterchain.KavaTestChainId)},
+ {Name: "gaia", Version: "v15.2.0", ChainConfig: ibc.ChainConfig{GasPrices: "0.0uatom"}},
+ })
+
+ chains, err := cf.Chains(t.Name())
+ require.NoError(t, err)
+
+ ictKava := chains[0].(*cosmos.CosmosChain)
+ gaia := chains[1].(*cosmos.CosmosChain)
+
+ client, network := interchaintest.DockerSetup(t)
+
+ r := interchaintest.NewBuiltinRelayerFactory(ibc.CosmosRly, zaptest.NewLogger(t)).
+ Build(t, client, network)
+
+ // configure interchain
+ const kavaGaiaIbcPath = "kava-gaia-ibc"
+ ic := interchaintest.NewInterchain().AddChain(ictKava).
+ AddChain(gaia).
+ AddRelayer(r, "relayer").
+ AddLink(interchaintest.InterchainLink{
+ Chain1: ictKava,
+ Chain2: gaia,
+ Relayer: r,
+ Path: kavaGaiaIbcPath,
+ })
+
+ // Log location
+ f, err := interchaintest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix()))
+ require.NoError(t, err)
+ // Reporter/logs
+ rep := testreporter.NewReporter(f)
+ eRep := rep.RelayerExecReporter(t)
+
+ // Build interchain
+ err = ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{
+ TestName: t.Name(),
+ Client: client,
+ NetworkID: network,
+ SkipPathCreation: false},
+ )
+ require.NoError(t, err)
+
+ // Create and Fund User Wallets
+ fundAmount := math.NewInt(1e12)
+
+ users := interchaintest.GetAndFundTestUsers(t, ctx, t.Name(), fundAmount, ictKava, gaia)
+ kavaUser := users[0]
+ gaiaUser := users[1]
+
+ // wait for new block to ensure initial funding complete
+ height, err := ictKava.Height(ctx)
+ require.NoError(t, err)
+ h := height
+ for h <= height {
+ h, err = ictKava.Height(ctx)
+ require.NoError(t, err)
+ }
+
+ gaiaChannelInfo, err := r.GetChannels(ctx, eRep, gaia.Config().ChainID)
+ require.NoError(t, err)
+ gaiaToKavaChannelID := gaiaChannelInfo[0].ChannelID
+ kavaToGaiaChannelID := gaiaChannelInfo[0].Counterparty.ChannelID
+
+ // for simplified management of the chain, use kava's e2e framework for account management
+ // this skirts problems in interchaintest with needing coin type 60
+ // there are exceptions in the relayer & ibc channel management that complicate setting the chain
+ // default coin type to 60 in the chain config.
+ // we need to fund an account and then all of kava's e2e testutil chain management will work.
+
+ rpcUrl, err := ictKava.FullNodes[0].GetHostAddress(ctx, "26657/tcp")
+ require.NoError(t, err, "failed to find rpc URL")
+ grpcUrl, err := ictKava.FullNodes[0].GetHostAddress(ctx, "9090/tcp")
+ require.NoError(t, err, "failed to find grpc URL")
+ evmUrl, err := ictKava.FullNodes[0].GetHostAddress(ctx, "8545/tcp")
+ require.NoError(t, err, "failed to find evm URL")
+
+ evmClient, err := ethclient.Dial(evmUrl)
+ require.NoError(t, err, "failed to connect to evm")
+
+ // create a funded evm account to initialize the testutil.Chain
+ // use testutil.Chain for account management because the accounts play nicely with EVM & SDK sides
+ deployerMnemonic, err := util.RandomMnemonic()
+ require.NoError(t, err)
+ evmDeployer, err := util.NewEvmSignerFromMnemonic(evmClient, big.NewInt(kavainterchain.KavaEvmTestChainId), deployerMnemonic)
+ require.NoError(t, err)
+
+ deployerKavaAddr := util.EvmToSdkAddress(evmDeployer.Address())
+ err = ictKava.SendFunds(ctx, kavaUser.KeyName(), ibc.WalletAmount{
+ Address: deployerKavaAddr.String(),
+ Denom: "ukava",
+ Amount: math.NewInt(1e10),
+ })
+ require.NoError(t, err)
+
+ // initialize testutil.Chain for account & tx management on both sdk & evm
+ kava, err := testutil.NewChain(t, &runner.ChainDetails{
+ RpcUrl: rpcUrl,
+ GrpcUrl: grpcUrl,
+ EvmRpcUrl: evmUrl,
+ ChainId: kavainterchain.KavaTestChainId,
+ StakingDenom: "ukava",
+ }, deployerMnemonic)
+ require.NoError(t, err)
+
+ deployer := kava.GetAccount("whale")
+
+ // deploy ERC20 contract
+ usdtAddr, deployTx, usdt, err := erc20.DeployErc20(
+ deployer.EvmAuth, kava.EvmClient,
+ "Test Tether USD", "USDT", 6,
+ )
+ require.NoError(t, err)
+ require.NotNil(t, usdtAddr)
+ require.NotNil(t, usdt)
+
+ _, err = util.WaitForEvmTxReceipt(kava.EvmClient, deployTx.Hash(), 10*time.Second)
+ require.NoError(t, err)
+
+ ////////////////////////////////////////////
+ // enable conversion from erc20 -> sdk.Coin
+ // (assumes there are none pre-configured!)
+ ////////////////////////////////////////////
+ // 1. Submit Proposal
+ sdkDenom := "tether/usdt"
+ rawCps, err := json.Marshal(evmutiltypes.NewConversionPairs(
+ evmutiltypes.NewConversionPair(
+ evmutiltypes.NewInternalEVMAddress(usdtAddr),
+ sdkDenom,
+ ),
+ ))
+ require.NoError(t, err)
+
+ paramChange := paramsutils.ParamChangeProposalJSON{
+ Title: "Enable erc20 conversion to sdk.Coin",
+ Description: ".",
+ Changes: paramsutils.ParamChangesJSON{
+ paramsutils.ParamChangeJSON{
+ Subspace: "evmutil",
+ Key: "EnabledConversionPairs",
+ Value: rawCps,
+ },
+ },
+ Deposit: "10000000ukava",
+ }
+
+ _, err = legacyParamChangeProposal(ictKava.FullNodes[0], ctx, kavaUser.KeyName(), ¶mChange)
+ require.NoError(t, err, "error submitting param change proposal tx")
+
+ // TODO: query proposal id. assuming it is 1 here.
+ propId := int64(1)
+
+ // 2. Vote on Proposal
+ err = ictKava.VoteOnProposalAllValidators(ctx, propId, cosmos.ProposalVoteYes)
+ require.NoError(t, err, "failed to submit votes")
+
+ height, _ = ictKava.Height(ctx)
+ _, err = cosmos.PollForProposalStatus(ctx, ictKava, height, height+10, propId, gov1beta1.StatusPassed)
+ require.NoError(t, err, "proposal status did not change to passed in expected number of blocks")
+
+ // fund a user & mint them some usdt
+ user := kava.NewFundedAccount("tether-user", sdk.NewCoins(sdk.NewCoin("ukava", math.NewInt(1e7))))
+ erc20FundAmt := big.NewInt(100e6)
+ mintTx, err := usdt.Mint(deployer.EvmAuth, user.EvmAddress, erc20FundAmt)
+ require.NoError(t, err)
+
+ _, err = util.WaitForEvmTxReceipt(kava.EvmClient, mintTx.Hash(), 10*time.Second)
+ require.NoError(t, err)
+ // verify they have erc20 balance!
+ bal, err := usdt.BalanceOf(nil, user.EvmAddress)
+ require.NoError(t, err)
+ require.Equal(t, erc20FundAmt, bal)
+
+ // convert the erc20 to sdk.Coin!
+ amountToConvert := math.NewInt(50e6)
+ msg := evmutiltypes.NewMsgConvertERC20ToCoin(
+ evmutiltypes.NewInternalEVMAddress(user.EvmAddress),
+ user.SdkAddress,
+ evmutiltypes.NewInternalEVMAddress(usdtAddr),
+ amountToConvert,
+ )
+ convertTx := util.KavaMsgRequest{
+ Msgs: []sdk.Msg{&msg},
+ GasLimit: 4e5,
+ FeeAmount: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(400))),
+ Data: "converting sdk coin to erc20",
+ }
+ res := user.SignAndBroadcastKavaTx(convertTx)
+ require.NoError(t, res.Err)
+
+ // check balance of cosmos coin!
+ sdkBalance := kava.QuerySdkForBalances(user.SdkAddress)
+ require.Equal(t, amountToConvert, sdkBalance.AmountOf(sdkDenom))
+
+ // IBC the newly minted sdk.Coin to gaia
+ dstAddress := gaiaUser.FormattedAddress()
+ transfer := ibc.WalletAmount{
+ Address: dstAddress,
+ Denom: ictKava.Config().Denom,
+ Amount: amountToConvert,
+ }
+ _, err = ictKava.SendIBCTransfer(ctx, kavaToGaiaChannelID, kavaUser.KeyName(), transfer, ibc.TransferOptions{})
+ require.NoError(t, err)
+
+ // manually flush packets so we don't need to wait for the relayer
+ require.NoError(t, r.Flush(ctx, eRep, kavaGaiaIbcPath, kavaToGaiaChannelID))
+
+ // determine IBC denom & check gaia balance
+ srcDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", gaiaToKavaChannelID, ictKava.Config().Denom))
+ erc20OnGaiaDenom := srcDenomTrace.IBCDenom()
+
+ gaiaBal, err := gaia.GetBalance(ctx, dstAddress, erc20OnGaiaDenom)
+ require.NoError(t, err)
+
+ require.Equal(t, amountToConvert, gaiaBal)
+}
+
+// copied from https://github.com/strangelove-ventures/interchaintest/blob/7272afc780da6e2c99b2d2b3d084d5a3b1c6895f/chain/cosmos/chain_node.go#L1292
+// but changed "submit-proposal" to "submit-legacy-proposal"
+func legacyParamChangeProposal(tn *cosmos.ChainNode, ctx context.Context, keyName string, prop *paramsutils.ParamChangeProposalJSON) (string, error) {
+ content, err := json.Marshal(prop)
+ if err != nil {
+ return "", err
+ }
+
+ hash := sha256.Sum256(content)
+ proposalFilename := fmt.Sprintf("%x.json", hash)
+ err = tn.WriteFile(ctx, content, proposalFilename)
+ if err != nil {
+ return "", fmt.Errorf("writing param change proposal: %w", err)
+ }
+
+ proposalPath := filepath.Join(tn.HomeDir(), proposalFilename)
+
+ command := []string{
+ "gov", "submit-legacy-proposal",
+ "param-change",
+ proposalPath,
+ }
+
+ return tn.ExecTx(ctx, keyName, command...)
+}
diff --git a/tests/e2e-ibc/go.mod b/tests/e2e-ibc/go.mod
new file mode 100644
index 00000000..e5e6c1f1
--- /dev/null
+++ b/tests/e2e-ibc/go.mod
@@ -0,0 +1,297 @@
+module github.com/kava-labs/kava/tests/interchain
+
+go 1.21.9
+
+require (
+ cosmossdk.io/math v1.3.0
+ github.com/cosmos/ibc-go/v7 v7.4.0
+ github.com/ethereum/go-ethereum v1.10.26
+ github.com/stretchr/testify v1.9.0
+ go.uber.org/zap v1.26.0
+)
+
+require (
+ github.com/cosmos/cosmos-sdk v0.47.10
+ github.com/kava-labs/kava v0.0.0-00010101000000-000000000000
+ github.com/strangelove-ventures/interchaintest/v7 v7.0.1-0.20240506191732-71a15c29f2b7
+)
+
+require (
+ cloud.google.com/go v0.112.0 // indirect
+ cloud.google.com/go/compute v1.24.0 // indirect
+ cloud.google.com/go/compute/metadata v0.2.3 // indirect
+ cloud.google.com/go/iam v1.1.6 // indirect
+ cloud.google.com/go/storage v1.36.0 // indirect
+ cosmossdk.io/api v0.3.1 // indirect
+ cosmossdk.io/core v0.6.1 // indirect
+ cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
+ cosmossdk.io/errors v1.0.1 // indirect
+ cosmossdk.io/log v1.3.1 // indirect
+ cosmossdk.io/tools/rosetta v0.2.1 // indirect
+ filippo.io/edwards25519 v1.0.0 // indirect
+ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
+ github.com/99designs/keyring v1.2.2 // indirect
+ github.com/BurntSushi/toml v1.3.2 // indirect
+ github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect
+ github.com/DataDog/zstd v1.5.5 // indirect
+ github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect
+ github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect
+ github.com/Microsoft/go-winio v0.6.1 // indirect
+ github.com/StackExchange/wmi v1.2.1 // indirect
+ github.com/StirlingMarketingGroup/go-namecase v1.0.0 // indirect
+ github.com/VictoriaMetrics/fastcache v1.12.1 // indirect
+ github.com/armon/go-metrics v0.4.1 // indirect
+ github.com/avast/retry-go/v4 v4.5.0 // indirect
+ github.com/aws/aws-sdk-go v1.44.203 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
+ github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
+ github.com/btcsuite/btcd v0.24.0 // indirect
+ github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
+ github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
+ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
+ github.com/cenkalti/backoff/v4 v4.1.3 // indirect
+ github.com/cespare/xxhash v1.1.0 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/chzyer/readline v1.5.1 // indirect
+ github.com/cockroachdb/errors v1.11.1 // indirect
+ github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
+ github.com/cockroachdb/pebble v1.1.0 // indirect
+ github.com/cockroachdb/redact v1.1.5 // indirect
+ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
+ github.com/coinbase/rosetta-sdk-go/types v1.0.0 // indirect
+ github.com/cometbft/cometbft v0.37.9 // indirect
+ github.com/cometbft/cometbft-db v0.9.1 // indirect
+ github.com/confio/ics23/go v0.9.0 // indirect
+ github.com/cosmos/btcutil v1.0.5 // indirect
+ github.com/cosmos/cosmos-db v1.0.2 // indirect
+ github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
+ github.com/cosmos/go-bip39 v1.0.0 // indirect
+ github.com/cosmos/gogogateway v1.2.0 // indirect
+ github.com/cosmos/gogoproto v1.4.10 // indirect
+ github.com/cosmos/iavl v1.2.0 // indirect
+ github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3 // indirect
+ github.com/cosmos/ibc-go/modules/capability v1.0.0-rc1 // indirect
+ github.com/cosmos/ics23/go v0.10.0 // indirect
+ github.com/cosmos/interchain-security/v3 v3.1.1-0.20231102122221-81650a84f989 // indirect
+ github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
+ github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect
+ github.com/creachadair/taskgroup v0.4.2 // indirect
+ github.com/danieljoos/wincred v1.1.2 // indirect
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+ github.com/deckarep/golang-set v1.8.0 // indirect
+ github.com/decred/base58 v1.0.4 // indirect
+ github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.1 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
+ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
+ github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
+ github.com/dgraph-io/ristretto v0.1.1 // indirect
+ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
+ github.com/docker/distribution v2.8.2+incompatible // indirect
+ github.com/docker/docker v24.0.7+incompatible // indirect
+ github.com/docker/go-connections v0.4.0 // indirect
+ github.com/docker/go-units v0.5.0 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
+ github.com/edsrzf/mmap-go v1.0.0 // indirect
+ github.com/emicklei/dot v1.6.1 // indirect
+ github.com/evmos/ethermint v0.21.0 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
+ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect
+ github.com/getsentry/sentry-go v0.27.0 // indirect
+ github.com/go-kit/kit v0.13.0 // indirect
+ github.com/go-kit/log v0.2.1 // indirect
+ github.com/go-logfmt/logfmt v0.6.0 // indirect
+ github.com/go-logr/logr v1.4.1 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
+ github.com/go-stack/stack v1.8.1 // indirect
+ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
+ github.com/gogo/googleapis v1.4.1 // indirect
+ github.com/gogo/protobuf v1.3.3 // indirect
+ github.com/golang/glog v1.2.0 // indirect
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+ github.com/golang/mock v1.6.0 // indirect
+ github.com/golang/protobuf v1.5.4 // indirect
+ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
+ github.com/google/btree v1.1.2 // indirect
+ github.com/google/go-cmp v0.6.0 // indirect
+ github.com/google/orderedcode v0.0.1 // indirect
+ github.com/google/s2a-go v0.1.7 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
+ github.com/googleapis/gax-go/v2 v2.12.0 // indirect
+ github.com/gorilla/handlers v1.5.1 // indirect
+ github.com/gorilla/mux v1.8.0 // indirect
+ github.com/gorilla/websocket v1.5.0 // indirect
+ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
+ github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
+ github.com/gtank/merlin v0.1.1 // indirect
+ github.com/gtank/ristretto255 v0.1.2 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-getter v1.7.5 // indirect
+ github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
+ github.com/hashicorp/go-safetemp v1.0.0 // indirect
+ github.com/hashicorp/go-version v1.6.0 // indirect
+ github.com/hashicorp/golang-lru v1.0.2 // indirect
+ github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/hdevalence/ed25519consensus v0.1.0 // indirect
+ github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
+ github.com/holiman/uint256 v1.2.4 // indirect
+ github.com/huandu/skiplist v1.2.0 // indirect
+ github.com/huin/goupnp v1.3.0 // indirect
+ github.com/iancoleman/orderedmap v0.2.0 // indirect
+ github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845 // indirect
+ github.com/improbable-eng/grpc-web v0.15.0 // indirect
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/ipfs/go-cid v0.4.1 // indirect
+ github.com/jackpal/go-nat-pmp v1.0.2 // indirect
+ github.com/jmespath/go-jmespath v0.4.0 // indirect
+ github.com/jmhodges/levigo v1.0.0 // indirect
+ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+ github.com/klauspost/compress v1.17.7 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+ github.com/kr/pretty v0.3.1 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/lib/pq v1.10.7 // indirect
+ github.com/libp2p/go-buffer-pool v0.1.0 // indirect
+ github.com/libp2p/go-libp2p v0.27.8 // indirect
+ github.com/linxGnu/grocksdb v1.8.13 // indirect
+ github.com/magiconair/properties v1.8.7 // indirect
+ github.com/manifoldco/promptui v0.9.0 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-runewidth v0.0.14 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
+ github.com/minio/highwayhash v1.0.2 // indirect
+ github.com/minio/sha256-simd v1.0.0 // indirect
+ github.com/misko9/go-substrate-rpc-client/v4 v4.0.0 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/mitchellh/go-testing-interface v1.14.1 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/mr-tron/base58 v1.2.0 // indirect
+ github.com/mtibben/percent v0.2.1 // indirect
+ github.com/multiformats/go-base32 v0.1.0 // indirect
+ github.com/multiformats/go-base36 v0.2.0 // indirect
+ github.com/multiformats/go-multiaddr v0.9.0 // indirect
+ github.com/multiformats/go-multibase v0.2.0 // indirect
+ github.com/multiformats/go-multicodec v0.8.1 // indirect
+ github.com/multiformats/go-multihash v0.2.1 // indirect
+ github.com/multiformats/go-varint v0.0.7 // indirect
+ github.com/olekukonko/tablewriter v0.0.5 // indirect
+ github.com/opencontainers/go-digest v1.0.0 // indirect
+ github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
+ github.com/pelletier/go-toml v1.9.5 // indirect
+ github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+ github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 // indirect
+ github.com/pierrec/xxHash v0.1.5 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/prometheus/client_golang v1.17.0 // indirect
+ github.com/prometheus/client_model v0.6.1 // indirect
+ github.com/prometheus/common v0.44.0 // indirect
+ github.com/prometheus/procfs v0.13.0 // indirect
+ github.com/prometheus/tsdb v0.7.1 // indirect
+ github.com/rakyll/statik v0.1.7 // indirect
+ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ github.com/rivo/uniseg v0.4.3 // indirect
+ github.com/rjeczalik/notify v0.9.1 // indirect
+ github.com/rogpeppe/go-internal v1.12.0 // indirect
+ github.com/rs/cors v1.8.3 // indirect
+ github.com/rs/zerolog v1.32.0 // indirect
+ github.com/sagikazarmark/locafero v0.4.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+ github.com/sasha-s/go-deadlock v0.3.1 // indirect
+ github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
+ github.com/spaolacci/murmur3 v1.1.0 // indirect
+ github.com/spf13/afero v1.11.0 // indirect
+ github.com/spf13/cast v1.6.0 // indirect
+ github.com/spf13/cobra v1.8.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/spf13/viper v1.18.2 // indirect
+ github.com/status-im/keycard-go v0.2.0 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
+ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
+ github.com/tendermint/go-amino v0.16.0 // indirect
+ github.com/tidwall/btree v1.7.0 // indirect
+ github.com/tklauser/go-sysconf v0.3.12 // indirect
+ github.com/tklauser/numcpus v0.6.1 // indirect
+ github.com/tyler-smith/go-bip32 v1.0.0 // indirect
+ github.com/tyler-smith/go-bip39 v1.1.0 // indirect
+ github.com/ulikunitz/xz v0.5.11 // indirect
+ github.com/vedhavyas/go-subkey/v2 v2.0.0 // indirect
+ github.com/zondax/hid v0.9.2 // indirect
+ github.com/zondax/ledger-go v0.14.3 // indirect
+ go.etcd.io/bbolt v1.3.8 // indirect
+ go.opencensus.io v0.24.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
+ go.opentelemetry.io/otel v1.22.0 // indirect
+ go.opentelemetry.io/otel/metric v1.22.0 // indirect
+ go.opentelemetry.io/otel/trace v1.22.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ golang.org/x/crypto v0.22.0 // indirect
+ golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
+ golang.org/x/mod v0.17.0 // indirect
+ golang.org/x/net v0.24.0 // indirect
+ golang.org/x/oauth2 v0.17.0 // indirect
+ golang.org/x/sync v0.7.0 // indirect
+ golang.org/x/sys v0.19.0 // indirect
+ golang.org/x/term v0.19.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+ golang.org/x/time v0.5.0 // indirect
+ golang.org/x/tools v0.20.0 // indirect
+ google.golang.org/api v0.162.0 // indirect
+ google.golang.org/appengine v1.6.8 // indirect
+ google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
+ google.golang.org/grpc v1.63.2 // indirect
+ google.golang.org/protobuf v1.33.0 // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
+ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ lukechampine.com/blake3 v1.1.7 // indirect
+ lukechampine.com/uint128 v1.2.0 // indirect
+ modernc.org/cc/v3 v3.40.0 // indirect
+ modernc.org/ccgo/v3 v3.16.13 // indirect
+ modernc.org/libc v1.24.1 // indirect
+ modernc.org/mathutil v1.5.0 // indirect
+ modernc.org/memory v1.6.0 // indirect
+ modernc.org/opt v0.1.3 // indirect
+ modernc.org/sqlite v1.25.0 // indirect
+ modernc.org/strutil v1.1.3 // indirect
+ modernc.org/token v1.0.1 // indirect
+ nhooyr.io/websocket v1.8.7 // indirect
+ pgregory.net/rapid v1.1.0 // indirect
+ sigs.k8s.io/yaml v1.4.0 // indirect
+)
+
+replace (
+ // use cosmos fork of keyring
+ github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0
+ // Use cometbft fork of tendermint
+ github.com/cometbft/cometbft => github.com/kava-labs/cometbft v0.37.9-kava.1
+ github.com/cometbft/cometbft-db => github.com/kava-labs/cometbft-db v0.9.1-kava.2
+ // Use cosmos-sdk fork with backported fix for unsafe-reset-all, staking transfer events, and custom tally handler support
+ github.com/cosmos/cosmos-sdk => github.com/kava-labs/cosmos-sdk v0.47.10-iavl-v1-kava.1
+ github.com/ethereum/go-ethereum => github.com/Kava-Labs/go-ethereum v1.10.27-0.20240308170502-da7973e5eee0
+ // Use ethermint fork that respects min-gas-price with NoBaseFee true and london enabled, and includes eip712 support
+ github.com/evmos/ethermint => github.com/kava-labs/ethermint v0.21.1-0.20240703152838-badab86e5aba
+ github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
+ // github.com/ComposableFi/go-subkey/v2 => github.com/vedhavyas/go-subkey/v2 v2.0.0
+ github.com/misko9/go-substrate-rpc-client/v4 => github.com/faddat/go-substrate-rpc-client/v4 v4.0.1-0.20240402155230-48db8c110afe
+
+ // stick with compatible version or x/exp in v0.47.x line
+ golang.org/x/exp => golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb
+)
+
+replace github.com/kava-labs/kava => ../../
diff --git a/tests/e2e-ibc/go.sum b/tests/e2e-ibc/go.sum
new file mode 100644
index 00000000..de6ee795
--- /dev/null
+++ b/tests/e2e-ibc/go.sum
@@ -0,0 +1,1942 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
+cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
+cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
+cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
+cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
+cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
+cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
+cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4=
+cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=
+cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=
+cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
+cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
+cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=
+cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
+cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=
+cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=
+cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=
+cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=
+cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=
+cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=
+cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=
+cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI=
+cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
+cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=
+cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
+cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
+cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=
+cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=
+cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=
+cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
+cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
+cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
+cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
+cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
+cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
+cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
+cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
+cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
+cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
+cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=
+cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=
+cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=
+cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=
+cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=
+cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=
+cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=
+cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=
+cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=
+cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=
+cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=
+cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=
+cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=
+cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8=
+cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=
+cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=
+cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=
+cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=
+cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=
+cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=
+cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=
+cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=
+cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=
+cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=
+cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=
+cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=
+cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=
+cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
+cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=
+cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
+cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
+cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
+cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
+cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
+cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=
+cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=
+cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=
+cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=
+cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=
+cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=
+cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=
+cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
+cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
+cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=
+cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=
+cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=
+cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=
+cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=
+cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=
+cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=
+cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=
+cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=
+cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=
+cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=
+cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=
+cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=
+cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=
+cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo=
+cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=
+cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=
+cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=
+cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=
+cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=
+cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=
+cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=
+cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=
+cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=
+cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=
+cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=
+cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=
+cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=
+cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
+cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=
+cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=
+cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=
+cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=
+cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
+cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
+cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
+cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
+cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
+cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
+cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
+cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=
+cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
+cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=
+cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=
+cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=
+cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=
+cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=
+cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
+cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
+cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
+cosmossdk.io/api v0.3.1 h1:NNiOclKRR0AOlO4KIqeaG6PS6kswOMhHD0ir0SscNXE=
+cosmossdk.io/api v0.3.1/go.mod h1:DfHfMkiNA2Uhy8fj0JJlOCYOBp4eWUUJ1te5zBGNyIw=
+cosmossdk.io/core v0.6.1 h1:OBy7TI2W+/gyn2z40vVvruK3di+cAluinA6cybFbE7s=
+cosmossdk.io/core v0.6.1/go.mod h1:g3MMBCBXtxbDWBURDVnJE7XML4BG5qENhs0gzkcpuFA=
+cosmossdk.io/depinject v1.0.0-alpha.4 h1:PLNp8ZYAMPTUKyG9IK2hsbciDWqna2z1Wsl98okJopc=
+cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vMaOmslGFM3yU=
+cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0=
+cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U=
+cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI=
+cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM=
+cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
+cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
+cosmossdk.io/simapp v0.0.0-20231127212628-044ff4d8c015 h1:ARUqouMWNreV8e5wxPberry+tm+Uk+1eeJDt3KXQEn0=
+cosmossdk.io/simapp v0.0.0-20231127212628-044ff4d8c015/go.mod h1:VNknW36ZIgwkjKtb6eyA4RZ7x9+ZpKMVCsAUA6bFWnk=
+cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw=
+cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw=
+filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
+filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
+github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
+github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM=
+github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4=
+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
+github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
+github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc=
+github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw=
+github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc=
+github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U=
+github.com/Kava-Labs/go-ethereum v1.10.27-0.20240308170502-da7973e5eee0 h1:pPFzOjEZmihLk70TQRPUCWs8uar6nfh4vZ/I1r0zeso=
+github.com/Kava-Labs/go-ethereum v1.10.27-0.20240308170502-da7973e5eee0/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
+github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
+github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
+github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
+github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
+github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
+github.com/StirlingMarketingGroup/go-namecase v1.0.0 h1:2CzaNtCzc4iNHirR+5ru9OzGg8rQp860gqLBFqRI02Y=
+github.com/StirlingMarketingGroup/go-namecase v1.0.0/go.mod h1:ZsoSKcafcAzuBx+sndbxHu/RjDcDTrEdT4UvhniHfio=
+github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40=
+github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o=
+github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
+github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
+github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I=
+github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg=
+github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
+github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
+github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
+github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
+github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
+github.com/avast/retry-go/v4 v4.5.0 h1:QoRAZZ90cj5oni2Lsgl2GW8mNTnUCnmpx/iKpwVisHg=
+github.com/avast/retry-go/v4 v4.5.0/go.mod h1:7hLEXp0oku2Nir2xBAsg0PTphp9z71bN5Aq1fboC3+I=
+github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go v1.44.203 h1:pcsP805b9acL3wUqa4JR2vg1k2wnItkDYNvfmcy6F+U=
+github.com/aws/aws-sdk-go v1.44.203/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
+github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s=
+github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
+github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
+github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
+github.com/btcsuite/btcd v0.24.0 h1:gL3uHE/IaFj6fcZSu03SvqPMSx7s/dPzfpG/atRwWdo=
+github.com/btcsuite/btcd v0.24.0/go.mod h1:K4IDc1593s8jKXIF7yS7yCTSxrknB9z0STzc2j6XgE4=
+github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
+github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
+github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
+github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
+github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
+github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
+github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
+github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
+github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
+github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ=
+github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
+github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
+github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
+github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
+github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
+github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
+github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
+github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
+github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
+github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
+github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
+github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
+github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
+github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
+github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw=
+github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
+github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
+github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
+github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
+github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
+github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
+github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
+github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
+github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
+github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
+github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
+github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
+github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
+github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
+github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
+github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
+github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
+github.com/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA=
+github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c=
+github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4=
+github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak=
+github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
+github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk=
+github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis=
+github.com/cosmos/cosmos-db v1.0.2 h1:hwMjozuY1OlJs/uh6vddqnk9j7VamLv+0DBlbEXbAKs=
+github.com/cosmos/cosmos-db v1.0.2/go.mod h1:Z8IXcFJ9PqKK6BIsVOB3QXtkKoqUOp1vRvPT39kOXEA=
+github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
+github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
+github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
+github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
+github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
+github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE=
+github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI=
+github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU=
+github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI=
+github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek=
+github.com/cosmos/iavl v1.2.0 h1:kVxTmjTh4k0Dh1VNL046v6BXqKziqMDzxo93oh3kOfM=
+github.com/cosmos/iavl v1.2.0/go.mod h1:HidWWLVAtODJqFD6Hbne2Y0q3SdxByJepHUOeoH4LiI=
+github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3 h1:MZGDMETv72suFpTAD6VPGqSIm1FJcChtk2HmVh9D+Bo=
+github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3/go.mod h1:UvDmcGIWJPIytq+Q78/ff5NTOsuX/7IrNgEugTW5i0s=
+github.com/cosmos/ibc-go/modules/capability v1.0.0-rc1 h1:BvSKnPFKxL+TTSLxGKwJN4x0ndCZj0yfXhSvmsQztSA=
+github.com/cosmos/ibc-go/modules/capability v1.0.0-rc1/go.mod h1:A+CxAQdn2j6ihDTbClpEEBdHthWgAUAcHbRAQPY8sl4=
+github.com/cosmos/ibc-go/v7 v7.4.0 h1:8FqYMptvksgMvlbN4UW9jFxTXzsPyfAzEZurujXac8M=
+github.com/cosmos/ibc-go/v7 v7.4.0/go.mod h1:L/KaEhzV5TGUCTfGysVgMBQtl5Dm7hHitfpk+GIeoAo=
+github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM=
+github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0=
+github.com/cosmos/interchain-security/v3 v3.1.1-0.20231102122221-81650a84f989 h1:Yk/2X33hHuS0mqjr4rE0ShiwPE/YflXgdyXPIYdwl+Q=
+github.com/cosmos/interchain-security/v3 v3.1.1-0.20231102122221-81650a84f989/go.mod h1:5B29fgUbUDTpBTqCnEzA2g3gI5rQG0YE/ir4isb2MEw=
+github.com/cosmos/keyring v1.2.0 h1:8C1lBP9xhImmIabyXW4c3vFjjLiBdGCmfLUfeZlV1Yo=
+github.com/cosmos/keyring v1.2.0/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA=
+github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM=
+github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8=
+github.com/cosmos/rosetta-sdk-go v0.10.0 h1:E5RhTruuoA7KTIXUcMicL76cffyeoyvNybzUGSKFTcM=
+github.com/cosmos/rosetta-sdk-go v0.10.0/go.mod h1:SImAZkb96YbwvoRkzSMQB6noNJXFgWl/ENIznEoYQI4=
+github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8=
+github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
+github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
+github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
+github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
+github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA=
+github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E=
+github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU=
+github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
+github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
+github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
+github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.1 h1:18HurQ6DfHeNvwIjvOmrgr44bPdtVaQAe/WWwHg9goM=
+github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.1/go.mod h1:XmyzkaXBy7ZvHdrTAlXAjpog8qKSAWa3ze7yqzWmgmc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
+github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
+github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I=
+github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE=
+github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
+github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
+github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
+github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
+github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
+github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
+github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
+github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ=
+github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
+github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/emicklei/dot v1.6.1 h1:ujpDlBkkwgWUY+qPId5IwapRW/xEoligRSYjioR6DFI=
+github.com/emicklei/dot v1.6.1/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
+github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
+github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
+github.com/faddat/go-substrate-rpc-client/v4 v4.0.1-0.20240402155230-48db8c110afe h1:63rMpjrCwbJVYoz1XFOzRXCZd8frLSuChg5XSo3tKg4=
+github.com/faddat/go-substrate-rpc-client/v4 v4.0.1-0.20240402155230-48db8c110afe/go.mod h1:enTNfc1aCsOpHnOqlDEDAGr3SzacyTh/VX/nL+5YBs4=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
+github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
+github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
+github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
+github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
+github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
+github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
+github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
+github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
+github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
+github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
+github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
+github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
+github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
+github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
+github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
+github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
+github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
+github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
+github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
+github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
+github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
+github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
+github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
+github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
+github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
+github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
+github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
+github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us=
+github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b h1:Qcx5LM0fSiks9uCyFZwDBUasd3lxd1RM0GYpL+Li5o4=
+github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
+github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
+github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
+github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
+github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
+github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
+github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
+github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
+github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
+github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
+github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
+github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
+github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
+github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
+github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
+github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
+github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc=
+github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
+github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
+github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
+github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4=
+github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
+github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
+github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
+github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
+github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
+github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
+github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
+github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c=
+github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
+github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw=
+github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w=
+github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
+github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
+github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
+github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
+github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845 h1:H+uM0Bv88eur3ZSsd2NGKg3YIiuXxwxtlN7HjE66UTU=
+github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845/go.mod h1:c1tRKs5Tx7E2+uHGSyyncziFjvGpgv4H2HrqXeUQ/Uk=
+github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ=
+github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
+github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
+github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
+github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
+github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
+github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
+github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kava-labs/cometbft v0.37.9-kava.1 h1:0mMsAhpV8p0peD9sabIZ//M4nP6LiiZ0o34gfuHdANY=
+github.com/kava-labs/cometbft v0.37.9-kava.1/go.mod h1:j0Q3RqrCd+cztWCugs3obbzC4NyHGBPZZjtm/fWV00I=
+github.com/kava-labs/cometbft-db v0.9.1-kava.2 h1:ZQaio886ifvml9XtJB4IYHhlArgA3+/a5Zwidg7H2J8=
+github.com/kava-labs/cometbft-db v0.9.1-kava.2/go.mod h1:PvUZbx7zeR7I4CAvtKBoii/5ia5gXskKjDjIVpt7gDw=
+github.com/kava-labs/cosmos-sdk v0.47.10-iavl-v1-kava.1 h1:vQwrm3sdAG1pkwrsi2mmCHSGDje5fzUR6vApEux/nVA=
+github.com/kava-labs/cosmos-sdk v0.47.10-iavl-v1-kava.1/go.mod h1:OwLYEBcsnijCLE8gYkwQ7jycZZ/Acd+a83pJU+V+MKw=
+github.com/kava-labs/ethermint v0.21.1-0.20240703152838-badab86e5aba h1:qrBo1h72L9WxZ7nUdpC7KlkTnscUfwWZAcZI1fWyMwE=
+github.com/kava-labs/ethermint v0.21.1-0.20240703152838-badab86e5aba/go.mod h1:/Gxb9qs4shgPP2FQMvm2DkZ/7csmEGUh7F5vl7fKQZw=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
+github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
+github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
+github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
+github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
+github.com/libp2p/go-libp2p v0.27.8 h1:IX5x/4yKwyPQeVS2AXHZ3J4YATM9oHBGH1gBc23jBAI=
+github.com/libp2p/go-libp2p v0.27.8/go.mod h1:eCFFtd0s5i/EVKR7+5Ki8bM7qwkNW3TPTTSSW9sz8NE=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
+github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
+github.com/linxGnu/grocksdb v1.8.13 h1:X3Id7Obhf8qLY9WPc4LmmtIyabmdDf810XSFDnLlW7E=
+github.com/linxGnu/grocksdb v1.8.13/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
+github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
+github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
+github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
+github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaGHBu1fghwxIPiopAHV06JlXrMHjk=
+github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU=
+github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
+github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
+github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
+github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
+github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
+github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
+github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
+github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
+github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
+github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
+github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
+github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
+github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
+github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ=
+github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0=
+github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
+github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
+github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8=
+github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
+github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108=
+github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc=
+github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
+github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
+github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
+github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
+github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
+github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
+github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
+github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
+github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w=
+github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
+github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
+github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA=
+github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
+github.com/oxyno-zeta/gomock-extra-matcher v1.1.0 h1:Yyk5ov0ZPKBXtVEeIWtc4J2XVrHuNoIK+0F2BUJgtsc=
+github.com/oxyno-zeta/gomock-extra-matcher v1.1.0/go.mod h1:UMGTHYEmJ1dRq8LDZ7VTAYO4nqM3GD1UGC3RJEUxEz0=
+github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
+github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
+github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 h1:jik8PHtAIsPlCRJjJzl4udgEf7hawInF9texMeO2jrU=
+github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
+github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/xxHash v0.1.5 h1:n/jBpwTHiER4xYvK3/CdPVnLDPchj8eTJFFLUb4QHBo=
+github.com/pierrec/xxHash v0.1.5/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I=
+github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
+github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
+github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
+github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
+github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
+github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
+github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4=
+github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
+github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
+github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo=
+github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
+github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
+github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
+github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
+github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
+github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg=
+github.com/strangelove-ventures/interchaintest/v7 v7.0.1-0.20240506191732-71a15c29f2b7 h1:7Qsz/NHs+FqDHvjHB3qLIMU7+/Z8vPI8ZJ2Q0p7x93U=
+github.com/strangelove-ventures/interchaintest/v7 v7.0.1-0.20240506191732-71a15c29f2b7/go.mod h1:J1Pbldeib6FKgQmEZvlaqvVNYDmQxVpR49KMBfMh8Z4=
+github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
+github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
+github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
+github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
+github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
+github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
+github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
+github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
+github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
+github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
+github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/tyler-smith/go-bip32 v1.0.0 h1:sDR9juArbUgX+bO/iblgZnMPeWY1KZMUC2AFUJdv5KE=
+github.com/tyler-smith/go-bip32 v1.0.0/go.mod h1:onot+eHknzV4BVPwrzqY5OoVpyCvnwD7lMawL5aQupE=
+github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
+github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
+github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
+github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
+github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
+github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y=
+github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
+github.com/vedhavyas/go-subkey/v2 v2.0.0 h1:LemDIsrVtRSOkp0FA8HxP6ynfKjeOj3BY2U9UNfeDMA=
+github.com/vedhavyas/go-subkey/v2 v2.0.0/go.mod h1:95aZ+XDCWAUUynjlmi7BtPExjXgXxByE0WfBwbmIRH4=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
+github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
+github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw=
+github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
+go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
+go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
+go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
+go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
+go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
+go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
+go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
+go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
+go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
+go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
+go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
+go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU=
+go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
+go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
+go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
+golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
+golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us=
+golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
+golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
+golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
+golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
+golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
+golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
+google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
+google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
+google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
+google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
+google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=
+google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
+google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
+google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps=
+google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
+google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
+google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
+google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
+google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
+google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
+google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
+google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
+google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
+google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
+gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
+gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54=
+launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
+lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
+lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
+lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
+lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
+modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
+modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
+modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
+modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
+modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
+modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
+modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
+modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
+modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
+modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
+modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
+modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
+modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA=
+modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
+modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
+modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
+modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
+modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
+modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
+modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
+modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
+nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
+nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
+nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
+pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
+pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
+sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
diff --git a/tests/e2e-ibc/main_test.go b/tests/e2e-ibc/main_test.go
new file mode 100644
index 00000000..8d24769a
--- /dev/null
+++ b/tests/e2e-ibc/main_test.go
@@ -0,0 +1,206 @@
+package main_test
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "cosmossdk.io/math"
+ transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+ "github.com/strangelove-ventures/interchaintest/v7"
+ "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos"
+ "github.com/strangelove-ventures/interchaintest/v7/ibc"
+ "github.com/strangelove-ventures/interchaintest/v7/testreporter"
+
+ "github.com/stretchr/testify/require"
+ "go.uber.org/zap/zaptest"
+
+ kavainterchain "github.com/kava-labs/kava/tests/interchain"
+)
+
+func TestInterchainIBC(t *testing.T) {
+ ctx := context.Background()
+
+ // setup chains
+ cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{
+ {Name: "kava", ChainConfig: kavainterchain.DefaultKavaChainConfig(kavainterchain.KavaTestChainId)},
+ {Name: "gaia", Version: "v15.2.0", ChainConfig: ibc.ChainConfig{GasPrices: "0.0uatom"}},
+ {Name: "osmosis", Version: "v24.0.1"},
+ })
+
+ chains, err := cf.Chains(t.Name())
+ require.NoError(t, err)
+
+ kava, gaia, osmosis := chains[0].(*cosmos.CosmosChain), chains[1].(*cosmos.CosmosChain), chains[2].(*cosmos.CosmosChain)
+
+ // setup relayer
+ client, network := interchaintest.DockerSetup(t)
+ r := interchaintest.NewBuiltinRelayerFactory(ibc.CosmosRly, zaptest.NewLogger(t)).
+ Build(t, client, network)
+
+ // configure interchain
+ const kavaGaiaIbcPath = "kava-gaia-demo"
+ const kavaOsmosisIbcPath = "kava-osmo-demo"
+ ic := interchaintest.NewInterchain().
+ AddChain(kava).
+ AddChain(gaia).
+ AddChain(osmosis).
+ AddRelayer(r, "relayer").
+ AddLink(interchaintest.InterchainLink{
+ Chain1: kava,
+ Chain2: gaia,
+ Relayer: r,
+ Path: kavaGaiaIbcPath,
+ }).
+ AddLink(interchaintest.InterchainLink{
+ Chain1: kava,
+ Chain2: osmosis,
+ Relayer: r,
+ Path: kavaOsmosisIbcPath,
+ })
+
+ // Log location
+ f, err := interchaintest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix()))
+ require.NoError(t, err)
+ // Reporter/logs
+ rep := testreporter.NewReporter(f)
+ eRep := rep.RelayerExecReporter(t)
+
+ // Build interchain
+ err = ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{
+ TestName: t.Name(),
+ Client: client,
+ NetworkID: network,
+ SkipPathCreation: false},
+ )
+ require.NoError(t, err)
+
+ // start the relayer so we don't need to manually Flush() packets
+ err = r.StartRelayer(ctx, eRep, kavaGaiaIbcPath, kavaOsmosisIbcPath)
+ require.NoError(t, err)
+ defer r.StopRelayer(ctx, eRep)
+
+ // Create and Fund User Wallets
+ fundAmount := math.NewInt(10_000_000)
+
+ users := interchaintest.GetAndFundTestUsers(t, ctx, "default", fundAmount, kava, gaia, osmosis)
+ kavaUser := users[0]
+ gaiaUser := users[1]
+ osmosisUser := users[2]
+
+ // wait for new block to ensure initial funding complete
+ height, err := kava.Height(ctx)
+ require.NoError(t, err)
+ h := height
+ for h <= height {
+ h, err = kava.Height(ctx)
+ require.NoError(t, err)
+ }
+
+ // check initial balance
+ kavaUserBalInitial, err := kava.GetBalance(ctx, kavaUser.FormattedAddress(), kava.Config().Denom)
+ require.NoError(t, err)
+ require.True(t, kavaUserBalInitial.Equal(fundAmount))
+
+ // get ibc channel ids
+ gaiaChannelInfo, err := r.GetChannels(ctx, eRep, gaia.Config().ChainID)
+ require.NoError(t, err)
+ osmoChannelInfo, err := r.GetChannels(ctx, eRep, osmosis.Config().ChainID)
+ require.NoError(t, err)
+
+ gaiaToKavaChannelID := gaiaChannelInfo[0].ChannelID
+ kavaToGaiaChannelID := gaiaChannelInfo[0].Counterparty.ChannelID
+ osmoToKavaChannelID := osmoChannelInfo[0].ChannelID
+ kavaToOsmoChannelID := osmoChannelInfo[0].Counterparty.ChannelID
+
+ // determine ibc denoms
+ srcDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", gaiaToKavaChannelID, kava.Config().Denom))
+ kavaOnGaiaDenom := srcDenomTrace.IBCDenom()
+ srcDenomTrace = transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", osmoToKavaChannelID, kava.Config().Denom))
+ kavaOnOsmoDenom := srcDenomTrace.IBCDenom()
+
+ amountToSend := math.NewInt(1_000_000)
+
+ // IBC transfer kava to cosmoshub
+ // inspired by https://github.com/strangelove-ventures/interchaintest/blob/main/examples/ibc/learn_ibc_test.go
+ t.Run("ibc transfer kava -> cosmoshub", func(t *testing.T) {
+ dstAddress := gaiaUser.FormattedAddress()
+ transfer := ibc.WalletAmount{
+ Address: dstAddress,
+ Denom: kava.Config().Denom,
+ Amount: amountToSend,
+ }
+
+ tx, err := kava.SendIBCTransfer(ctx, kavaToGaiaChannelID, kavaUser.KeyName(), transfer, ibc.TransferOptions{})
+ require.NoError(t, err)
+ require.NoError(t, tx.Validate())
+
+ // manually flush packets so we don't need to wait for the relayer
+ require.NoError(t, r.Flush(ctx, eRep, kavaGaiaIbcPath, kavaToGaiaChannelID))
+
+ // verify balance deducted from kava account
+ expectedBal := kavaUserBalInitial.Sub(amountToSend)
+ kavaUserBalNew, err := kava.GetBalance(ctx, kavaUser.FormattedAddress(), kava.Config().Denom)
+ require.NoError(t, err)
+ require.True(t, kavaUserBalNew.Equal(expectedBal))
+
+ // verify cosmoshub account received funds
+ gaiaUserBalNew, err := gaia.GetBalance(ctx, gaiaUser.FormattedAddress(), kavaOnGaiaDenom)
+ require.NoError(t, err)
+ require.True(t, gaiaUserBalNew.Equal(amountToSend))
+ })
+
+ // use coins IBC'd to cosmoshub, send them to osmosis using pfm
+ t.Run("packet forwarding middleware: cosmoshub -> kava -> osmosis", func(t *testing.T) {
+ dstAddress := osmosisUser.FormattedAddress()
+ transfer := ibc.WalletAmount{
+ Address: "pfm", // purposefully invalid b/c we are forwarding through kava onward to osmosis!
+ Denom: kavaOnGaiaDenom,
+ Amount: amountToSend,
+ }
+
+ tx, err := gaia.SendIBCTransfer(ctx, gaiaToKavaChannelID, gaiaUser.KeyName(), transfer, ibc.TransferOptions{
+ // packet forwarding middleware!
+ Memo: fmt.Sprintf(`{
+ "forward": {
+ "receiver": "%s",
+ "port": "transfer",
+ "channel": "%s"
+ }
+ }`, dstAddress, kavaToOsmoChannelID),
+ })
+ require.NoError(t, err)
+ require.NoError(t, tx.Validate())
+
+ require.Eventually(t, func() bool {
+ // verify transfer to osmosis
+ osmosisUserBalNew, err := osmosis.GetBalance(ctx, osmosisUser.FormattedAddress(), kavaOnOsmoDenom)
+ require.NoError(t, err)
+ return osmosisUserBalNew.Equal(amountToSend)
+ }, 15*time.Second, time.Second, "osmosis never received funds")
+
+ // verify cosmoshub account no longer has the funds
+ gaiaUserBalNew, err := gaia.GetBalance(ctx, gaiaUser.FormattedAddress(), kavaOnGaiaDenom)
+ require.NoError(t, err)
+ require.True(t, gaiaUserBalNew.Equal(math.ZeroInt()))
+ })
+
+ t.Run("query evm data", func(t *testing.T) {
+ evmUrl, err := kava.FullNodes[0].GetHostAddress(ctx, "8545/tcp")
+ require.NoError(t, err)
+
+ evmClient, err := ethclient.Dial(evmUrl)
+ require.NoError(t, err, "failed to connect to evm")
+
+ bal, err := evmClient.BalanceAt(ctx, common.BytesToAddress(kavaUser.Address()), nil)
+ require.NoError(t, err)
+
+ // convert ukava to akava
+ expected := fundAmount.Sub(amountToSend).MulRaw(1e12)
+ require.Equal(t, expected.BigInt().String(), bal.String())
+ })
+}
diff --git a/tests/e2e/.env b/tests/e2e/.env
index 8c757f08..966c5d10 100644
--- a/tests/e2e/.env
+++ b/tests/e2e/.env
@@ -21,12 +21,12 @@ E2E_SKIP_SHUTDOWN=false
# E2E_INCLUDE_AUTOMATED_UPGRADE when true enables the automated upgrade & corresponding tests in the suite.
E2E_INCLUDE_AUTOMATED_UPGRADE=true
# E2E_KAVA_UPGRADE_NAME is the name of the upgrade that must be in the current local image.
-E2E_KAVA_UPGRADE_NAME=v0.26.0
+E2E_KAVA_UPGRADE_NAME=v0.27.0
# E2E_KAVA_UPGRADE_HEIGHT is the height at which the upgrade will be applied.
# If IBC tests are enabled this should be >30. Otherwise, this should be >10.
E2E_KAVA_UPGRADE_HEIGHT=35
# E2E_KAVA_UPGRADE_BASE_IMAGE_TAG is the tag of the docker image the chain should upgrade from.
-E2E_KAVA_UPGRADE_BASE_IMAGE_TAG=v0.25.1-goleveldb
+E2E_KAVA_UPGRADE_BASE_IMAGE_TAG=v0.26.0-goleveldb
# E2E_KAVA_ERC20_ADDRESS is the address of a pre-deployed ERC20 token with the following properties:
# - the E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC has nonzero balance
diff --git a/tests/e2e/e2e_upgrade_handler_test.go b/tests/e2e/e2e_upgrade_handler_test.go
index 4d79ddcb..f287ec78 100644
--- a/tests/e2e/e2e_upgrade_handler_test.go
+++ b/tests/e2e/e2e_upgrade_handler_test.go
@@ -1,127 +1,102 @@
package e2e_test
import (
- "time"
-
- tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
- tmtypes "github.com/cometbft/cometbft/types"
- sdk "github.com/cosmos/cosmos-sdk/types"
- consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types"
- govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
+ precisebanktypes "github.com/0glabs/0g-chain/x/precisebank/types"
+ banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)
-func (suite *IntegrationTestSuite) TestUpgradeParams_SDK() {
+func (suite *IntegrationTestSuite) TestUpgrade_PreciseBankReserveTransfer() {
suite.SkipIfUpgradeDisabled()
- beforeUpgradeCtx := suite.ZgChain.Grpc.CtxAtHeight(suite.UpgradeHeight - 1)
- afterUpgradeCtx := suite.ZgChain.Grpc.CtxAtHeight(suite.UpgradeHeight)
+ // beforeUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight - 1)
+ // afterUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight)
- // Before params
- grpcClient := suite.ZgChain.Grpc
- govParamsBefore, err := grpcClient.Query.Gov.Params(beforeUpgradeCtx, &govtypes.QueryParamsRequest{
- ParamsType: govtypes.ParamDeposit,
- })
- suite.NoError(err)
- govParamsAfter, err := grpcClient.Query.Gov.Params(afterUpgradeCtx, &govtypes.QueryParamsRequest{
- ParamsType: govtypes.ParamDeposit,
- })
- suite.NoError(err)
+ // grpcClient := suite.Kava.Grpc
- // after upgrade, querying params before upgrade height returns nil
- // since the param gprc query no longer queries x/params
- suite.Run("x/gov parameters before upgrade", func() {
- suite.Assert().Nil(
- govParamsBefore.DepositParams.MaxDepositPeriod,
- "x/gov DepositParams max deposit period before upgrade should be nil",
- )
- suite.Assert().Nil(
- govParamsBefore.DepositParams.MinDeposit,
- "x/gov DepositParams min deposit before upgrade should be 10_000_000 ukava",
- )
- })
+ // -----------------------------
+ // Get initial reserve balances
+ evmutilAddr := "kava1w9vxuke5dz6hyza2j932qgmxltnfxwl78u920k"
+ precisebankAddr := "kava12yfe2jaupmtjruwxsec7hg7er60fhaa4uz7ffl"
- suite.Run("x/gov parameters after upgrade", func() {
- suite.Assert().Equal(
- mustParseDuration("172800s"),
- govParamsAfter.DepositParams.MaxDepositPeriod,
- "x/gov DepositParams max deposit period after upgrade should be 172800s",
- )
- suite.Assert().Equal(
- []sdk.Coin{{Denom: "ua0gi", Amount: sdk.NewInt(10_000_000)}},
- govParamsAfter.DepositParams.MinDeposit,
- "x/gov DepositParams min deposit after upgrade should be 10_000_000 ukava",
- )
-
- expectedParams := govtypes.Params{
- MinDeposit: sdk.NewCoins(sdk.NewCoin("ua0gi", sdk.NewInt(10_000_000))),
- MaxDepositPeriod: mustParseDuration("172800s"),
- VotingPeriod: mustParseDuration("30s"),
- Quorum: "0.334000000000000000",
- Threshold: "0.500000000000000000",
- VetoThreshold: "0.334000000000000000",
- MinInitialDepositRatio: "0.000000000000000000",
- BurnVoteQuorum: false,
- BurnProposalDepositPrevote: false,
- BurnVoteVeto: true,
- }
- suite.Require().Equal(expectedParams, *govParamsAfter.Params, "x/gov params after upgrade should be as expected")
+ previousEvmutilBalRes, err := grpcClient.Query.Bank.Balance(beforeUpgradeCtx, &banktypes.QueryBalanceRequest{
+ Address: evmutilAddr,
+ Denom: precisebanktypes.IntegerCoinDenom,
})
-}
-
-func (suite *IntegrationTestSuite) TestUpgradeParams_Consensus() {
- suite.SkipIfUpgradeDisabled()
-
- afterUpgradeCtx := suite.ZgChain.Grpc.CtxAtHeight(suite.UpgradeHeight)
-
- grpcClient := suite.ZgChain.Grpc
- paramsAfter, err := grpcClient.Query.Consensus.Params(afterUpgradeCtx, &consensustypes.QueryParamsRequest{})
- suite.NoError(err)
-
- // v25 consensus params from x/params should be migrated to x/consensus
- expectedParams := tmproto.ConsensusParams{
- Block: &tmproto.BlockParams{
- MaxBytes: 22020096,
- MaxGas: 20000000,
- },
- Evidence: &tmproto.EvidenceParams{
- MaxAgeNumBlocks: 100000,
- MaxAgeDuration: *mustParseDuration("172800s"),
- MaxBytes: 1048576,
- },
- Validator: &tmproto.ValidatorParams{
- PubKeyTypes: []string{
- tmtypes.ABCIPubKeyTypeEd25519,
- },
- },
- Version: nil,
- }
- suite.Require().Equal(expectedParams, *paramsAfter.Params, "x/consensus params after upgrade should be as expected")
-}
-
-// func (suite *IntegrationTestSuite) TestUpgradeParams_CDP_Interval() {
-// suite.SkipIfUpgradeDisabled()
-
-// beforeUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight - 1)
-// afterUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight)
-
-// grpcClient := suite.Kava.Grpc
-
-// paramsBefore, err := grpcClient.Query.Cdp.Params(beforeUpgradeCtx, &cdptypes.QueryParamsRequest{})
-// suite.Require().NoError(err)
-// paramsAfter, err := grpcClient.Query.Cdp.Params(afterUpgradeCtx, &cdptypes.QueryParamsRequest{})
-// suite.Require().NoError(err)
-
-// expectedParams := paramsBefore.Params
-// expectedParams.LiquidationBlockInterval = int64(50)
-
-// suite.Require().Equal(expectedParams, paramsAfter.Params,
-// "expected cdp parameters to equal previous parameters with a liquidation block interval of 100")
-// }
-
-func mustParseDuration(s string) *time.Duration {
- d, err := time.ParseDuration(s)
- if err != nil {
- panic(err)
- }
- return &d
+ suite.Require().NoError(err)
+ suite.Require().NotNil(previousEvmutilBalRes.Balance)
+ suite.Require().True(
+ previousEvmutilBalRes.Balance.Amount.IsPositive(),
+ "should have reserve balance before upgrade",
+ )
+
+ previousPrecisebankBalRes, err := grpcClient.Query.Bank.Balance(beforeUpgradeCtx, &banktypes.QueryBalanceRequest{
+ Address: precisebankAddr,
+ Denom: precisebanktypes.IntegerCoinDenom,
+ })
+ suite.Require().NoError(err)
+ suite.Require().NotNil(previousPrecisebankBalRes.Balance)
+ suite.Require().True(
+ previousPrecisebankBalRes.Balance.Amount.IsZero(),
+ "should be empty before upgrade",
+ )
+
+ suite.T().Logf("x/evmutil balances before upgrade: %s", previousEvmutilBalRes.Balance)
+ suite.T().Logf("x/precisebank balances before upgrade: %s", previousPrecisebankBalRes.Balance)
+
+ // -----------------------------
+ // After upgrade
+ // - Check reserve balance transfer
+ // - Check reserve fully backs fractional amounts
+ afterEvmutilBalRes, err := grpcClient.Query.Bank.Balance(afterUpgradeCtx, &banktypes.QueryBalanceRequest{
+ Address: evmutilAddr,
+ Denom: precisebanktypes.IntegerCoinDenom,
+ })
+ suite.Require().NoError(err)
+ suite.Require().NotNil(afterEvmutilBalRes.Balance)
+ suite.Require().Truef(
+ afterEvmutilBalRes.Balance.Amount.IsZero(),
+ "should have transferred all reserve balance to precisebank, expected 0 but got %s",
+ afterEvmutilBalRes.Balance,
+ )
+
+ afterPrecisebankBalRes, err := grpcClient.Query.Bank.Balance(afterUpgradeCtx, &banktypes.QueryBalanceRequest{
+ Address: precisebankAddr,
+ Denom: precisebanktypes.IntegerCoinDenom,
+ })
+ suite.Require().NoError(err)
+ suite.Require().NotNil(afterPrecisebankBalRes.Balance)
+ // 2 total in reserve- genesis.json has 5 accounts with fractional balances
+ // totalling 2 integer coins
+ suite.Require().Equal(int64(2), afterPrecisebankBalRes.Balance.Amount.Int64())
+
+ suite.T().Logf("x/evmutil balances after upgrade: %s", afterEvmutilBalRes.Balance)
+ suite.T().Logf("x/precisebank balances after upgrade: %s", afterPrecisebankBalRes.Balance)
+
+ sumFractional, err := grpcClient.Query.Precisebank.TotalFractionalBalances(
+ afterUpgradeCtx,
+ &precisebanktypes.QueryTotalFractionalBalancesRequest{},
+ )
+ suite.Require().NoError(err)
+
+ suite.Require().Equal(
+ sumFractional.Total.Amount,
+ afterPrecisebankBalRes.Balance.Amount.Mul(precisebanktypes.ConversionFactor()),
+ "reserve should match exactly sum fractional balances",
+ )
+
+ // Check remainder + total fractional balances = reserve balance
+ remainderRes, err := grpcClient.Query.Precisebank.Remainder(
+ afterUpgradeCtx,
+ &precisebanktypes.QueryRemainderRequest{},
+ )
+ suite.Require().NoError(err)
+
+ sumFractionalAndRemainder := sumFractional.Total.Add(remainderRes.Remainder)
+ reserveBalanceExtended := afterPrecisebankBalRes.Balance.Amount.Mul(precisebanktypes.ConversionFactor())
+
+ suite.Require().Equal(
+ sumFractionalAndRemainder.Amount,
+ reserveBalanceExtended,
+ "remainder + sum(fractional balances) should be = reserve balance",
+ )
}
diff --git a/tests/e2e/kvtool b/tests/e2e/kvtool
index 79bb25f4..f78c7454 160000
--- a/tests/e2e/kvtool
+++ b/tests/e2e/kvtool
@@ -1 +1 @@
-Subproject commit 79bb25f47d20c10e70f0539c519240fba82b3302
+Subproject commit f78c74549f62d975d8de99c7bd158b3d92e5b145
diff --git a/tests/e2e/testutil/account.go b/tests/e2e/testutil/account.go
index 5e5659aa..5c29656d 100644
--- a/tests/e2e/testutil/account.go
+++ b/tests/e2e/testutil/account.go
@@ -13,11 +13,9 @@ import (
sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/crypto/hd"
- "github.com/cosmos/cosmos-sdk/crypto/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
- "github.com/cosmos/go-bip39"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
@@ -208,17 +206,15 @@ func (a *SigningAccount) SignAndBroadcastEvmTx(req util.EvmTxRequest) EvmTxRespo
// SignRawEvmData signs raw evm data with the SigningAccount's private key.
// It does not broadcast the signed data.
-func (a *SigningAccount) SignRawEvmData(msg []byte) ([]byte, types.PubKey, error) {
+func (a *SigningAccount) SignRawEvmData(msg []byte) ([]byte, cryptotypes.PubKey, error) {
keyringSigner := emtests.NewSigner(a.evmPrivKey)
return keyringSigner.SignByAddress(a.SdkAddress, msg)
}
// NewFundedAccount creates a SigningAccount for a random account & funds the account from the whale.
func (chain *Chain) NewFundedAccount(name string, funds sdk.Coins) *SigningAccount {
- entropy, err := bip39.NewEntropy(128)
- require.NoErrorf(chain.t, err, "failed to generate entropy for account %s: %s", name, err)
- mnemonic, err := bip39.NewMnemonic(entropy)
- require.NoErrorf(chain.t, err, "failed to create new mnemonic for account %s: %s", name, err)
+ mnemonic, err := util.RandomMnemonic()
+ require.NoError(chain.t, err)
acc := chain.AddNewSigningAccount(
name,
diff --git a/tests/e2e/testutil/suite.go b/tests/e2e/testutil/suite.go
index 80c299fe..bc656cca 100644
--- a/tests/e2e/testutil/suite.go
+++ b/tests/e2e/testutil/suite.go
@@ -217,7 +217,7 @@ func (suite *E2eTestSuite) SkipIfIbcDisabled() {
}
// SkipIfUpgradeDisabled should be called at the start of tests that require automated upgrades.
-// It gracefully skips the current test if upgrades are dissabled.
+// It gracefully skips the current test if upgrades are disabled.
// Note: automated upgrade tests are currently only enabled for Kvtool suite runs.
func (suite *E2eTestSuite) SkipIfUpgradeDisabled() {
if suite.config.Kvtool != nil && !suite.config.Kvtool.IncludeAutomatedUpgrade {
diff --git a/tests/util/addresses.go b/tests/util/addresses.go
index d8118d41..c0063828 100644
--- a/tests/util/addresses.go
+++ b/tests/util/addresses.go
@@ -1,7 +1,9 @@
package util
import (
+ errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/cosmos/go-bip39"
"github.com/ethereum/go-ethereum/common"
)
@@ -12,3 +14,12 @@ func SdkToEvmAddress(addr sdk.AccAddress) common.Address {
func EvmToSdkAddress(addr common.Address) sdk.AccAddress {
return sdk.AccAddress(addr.Bytes())
}
+
+// RandomMnemonic generates a random BIP39 mnemonic from 128 bits of entropy
+func RandomMnemonic() (string, error) {
+ entropy, err := bip39.NewEntropy(128)
+ if err != nil {
+ return "", errorsmod.Wrap(err, "failed to generate entropy for new mnemonic")
+ }
+ return bip39.NewMnemonic(entropy)
+}
diff --git a/tests/util/evmsigner.go b/tests/util/evmsigner.go
index 5b860ef9..9bb4fd80 100644
--- a/tests/util/evmsigner.go
+++ b/tests/util/evmsigner.go
@@ -3,17 +3,21 @@ package util
import (
"context"
"crypto/ecdsa"
+ "encoding/hex"
"errors"
"fmt"
"math/big"
"time"
+ errorsmod "cosmossdk.io/errors"
+ "github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
+ "github.com/evmos/ethermint/crypto/ethsecp256k1"
)
var (
@@ -77,6 +81,21 @@ func NewEvmSigner(
}, nil
}
+func NewEvmSignerFromMnemonic(evmClient *ethclient.Client, evmChainId *big.Int, mnemonic string) (*EvmSigner, error) {
+ hdPath := hd.CreateHDPath(60, 0, 0)
+ privKeyBytes, err := hd.Secp256k1.Derive()(mnemonic, "", hdPath.String())
+ if err != nil {
+ return nil, errorsmod.Wrap(err, "failed to derive private key from mnemonic")
+ }
+ privKey := ðsecp256k1.PrivKey{Key: privKeyBytes}
+ ecdsaPrivKey, err := crypto.HexToECDSA(hex.EncodeToString(privKey.Bytes()))
+ if err != nil {
+ return nil, err
+ }
+
+ return NewEvmSigner(evmClient, ecdsaPrivKey, evmChainId)
+}
+
func (s *EvmSigner) Run(requests <-chan EvmTxRequest) <-chan EvmTxResponse {
responses := make(chan EvmTxResponse)
diff --git a/tests/util/sdksigner.go b/tests/util/sdksigner.go
index d3473145..db364917 100644
--- a/tests/util/sdksigner.go
+++ b/tests/util/sdksigner.go
@@ -358,7 +358,7 @@ func (s *ZgChainSigner) Run(requests <-chan ZgChainMsgRequest) (<-chan ZgChainMs
currentRequest = nil
}
- // immediatley response to channel
+ // immediately response to channel
responses <- *response
// go to next request
broadcastTxSeq++
diff --git a/third_party/proto/cosmos_proto/cosmos.proto b/third_party/proto/cosmos_proto/cosmos.proto
index 5c63b86f..4fb58159 100644
--- a/third_party/proto/cosmos_proto/cosmos.proto
+++ b/third_party/proto/cosmos_proto/cosmos.proto
@@ -5,6 +5,12 @@ import "google/protobuf/descriptor.proto";
option go_package = "github.com/cosmos/cosmos-proto;cosmos_proto";
+extend google.protobuf.MethodOptions {
+
+ // method_added_in is used to indicate from which version the method was added.
+ string method_added_in = 93001;
+}
+
extend google.protobuf.MessageOptions {
// implements_interface is used to indicate the type name of the interface
@@ -13,6 +19,9 @@ extend google.protobuf.MessageOptions {
// interfaces. Interfaces should be declared using a declare_interface
// file option.
repeated string implements_interface = 93001;
+
+ // message_added_in is used to indicate from which version the message was added.
+ string message_added_in = 93002;
}
extend google.protobuf.FieldOptions {
@@ -27,6 +36,9 @@ extend google.protobuf.FieldOptions {
// generators may choose to use this information to map this field to a
// language-specific type representing the scalar.
string scalar = 93002;
+
+ // field_added_in is used to indicate from which version the field was added.
+ string field_added_in = 93003;
}
extend google.protobuf.FileOptions {
@@ -46,6 +58,9 @@ extend google.protobuf.FileOptions {
// expected that the declaration will be found in a protobuf file named
// a/b/scalars.proto in the file descriptor set.
repeated ScalarDescriptor declare_scalar = 793022;
+
+ // file_added_in is used to indicate from which the version the file was added.
+ string file_added_in = 793023;
}
// InterfaceDescriptor describes an interface type to be used with
@@ -94,4 +109,4 @@ enum ScalarType {
SCALAR_TYPE_UNSPECIFIED = 0;
SCALAR_TYPE_STRING = 1;
SCALAR_TYPE_BYTES = 2;
-}
+}
\ No newline at end of file
diff --git a/third_party/proto/ethermint/evm/v1/evm.proto b/third_party/proto/ethermint/evm/v1/evm.proto
index 875619ff..43b3e7a5 100644
--- a/third_party/proto/ethermint/evm/v1/evm.proto
+++ b/third_party/proto/ethermint/evm/v1/evm.proto
@@ -18,12 +18,17 @@ message Params {
repeated int64 extra_eips = 4 [(gogoproto.customname) = "ExtraEIPs", (gogoproto.moretags) = "yaml:\"extra_eips\""];
// chain_config defines the EVM chain configuration parameters
ChainConfig chain_config = 5 [(gogoproto.moretags) = "yaml:\"chain_config\"", (gogoproto.nullable) = false];
- // list of allowed eip712 msgs and their types
+ // eip712_allowed_msgs contains list of allowed eip712 msgs and their types
repeated EIP712AllowedMsg eip712_allowed_msgs = 6
[(gogoproto.customname) = "EIP712AllowedMsgs", (gogoproto.nullable) = false];
// allow_unprotected_txs defines if replay-protected (i.e non EIP155
// signed) transactions can be executed on the state machine.
bool allow_unprotected_txs = 7;
+ // enabled_precompiles contains list of hex-encoded evm addresses of enabled precompiled contracts.
+ // Precompile must be registered before it can be enabled.
+ // enabled_precompiles should be sorted in ascending order and unique.
+ // sorting and uniqueness are checked against bytes representation of addresses
+ repeated string enabled_precompiles = 8;
}
// ChainConfig defines the Ethereum ChainConfig parameters using *sdk.Int values
@@ -246,20 +251,20 @@ message TraceConfig {
// EIP712AllowedMsg stores an allowed legacy msg and its eip712 type.
message EIP712AllowedMsg {
- // msg's proto type name. ie "/cosmos.bank.v1beta1.MsgSend"
+ // msg_type_url is a msg's proto type name. ie "/cosmos.bank.v1beta1.MsgSend"
string msg_type_url = 1;
- // name of the eip712 value type. ie "MsgValueSend"
+ // msg_value_type_name is a name of the eip712 value type. ie "MsgValueSend"
string msg_value_type_name = 2;
- // types of the msg value
+ // value_types is a list of msg value types
repeated EIP712MsgAttrType value_types = 3 [(gogoproto.nullable) = false];
- // nested types of the msg value
+ // nested_types is a list of msg value nested types
repeated EIP712NestedMsgType nested_types = 4 [(gogoproto.nullable) = false];
}
-// EIP712MsgType is the eip712 type of a single message.
+// EIP712NestedMsgType is the eip712 type of a single message.
message EIP712NestedMsgType {
// name of the nested type. ie "Fee", "Coin"
string name = 1;
@@ -270,6 +275,8 @@ message EIP712NestedMsgType {
// EIP712MsgAttrType is the eip712 type of a single message attribute.
message EIP712MsgAttrType {
+ // name
string name = 1;
+ // type
string type = 2;
}
diff --git a/x/bep3/abci_test.go b/x/bep3/abci_test.go
index 0b15cb89..23d6448e 100644
--- a/x/bep3/abci_test.go
+++ b/x/bep3/abci_test.go
@@ -132,7 +132,7 @@ func (suite *ABCITestSuite) TestBeginBlocker_UpdateExpiredAtomicSwaps() {
// Run the second begin blocker
bep3.BeginBlocker(tc.secondCtx, suite.keeper)
- // Check each swap's availibility and status
+ // Check each swap's availability and status
for _, swapID := range suite.swapIDs {
storedSwap, found := suite.keeper.GetAtomicSwap(tc.secondCtx, swapID)
if tc.expectInStorage {
diff --git a/x/bep3/keeper/asset_test.go b/x/bep3/keeper/asset_test.go
index 6fd33023..f68f5e1a 100644
--- a/x/bep3/keeper/asset_test.go
+++ b/x/bep3/keeper/asset_test.go
@@ -431,7 +431,7 @@ func (suite *AssetTestSuite) TestIncrementOutgoingAssetSupply() {
true,
},
{
- "outoing + amount > current",
+ "outgoing + amount > current",
args{
coin: c("bnb", 36),
},
diff --git a/x/bep3/types/swap.go b/x/bep3/types/swap.go
index d5203223..aff0c50f 100644
--- a/x/bep3/types/swap.go
+++ b/x/bep3/types/swap.go
@@ -67,7 +67,7 @@ func (a AtomicSwap) Validate() error {
if a.Recipient.Empty() {
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "recipient cannot be empty")
}
- // NOTE: These adresses may not have a bech32 prefix.
+ // NOTE: These addresses may not have a bech32 prefix.
if strings.TrimSpace(a.SenderOtherChain) == "" {
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "sender other chain cannot be blank")
}
diff --git a/x/committee/keeper/proposal.go b/x/committee/keeper/proposal.go
index f1d922f9..b5307206 100644
--- a/x/committee/keeper/proposal.go
+++ b/x/committee/keeper/proposal.go
@@ -185,7 +185,7 @@ func (k Keeper) GetTokenCommitteeProposalResult(ctx sdk.Context, proposalID uint
return false
}
-// TallyMemberCommitteeVotes returns the polling status of a token committee vote. Returns yes votes,
+// TallyTokenCommitteeVotes returns the polling status of a token committee vote. Returns yes votes,
// total current votes, total possible votes (equal to token supply), vote threshold (yes vote ratio
// required for proposal to pass), and quorum (votes tallied at this percentage).
func (k Keeper) TallyTokenCommitteeVotes(ctx sdk.Context, proposalID uint64,
diff --git a/x/committee/types/committee.go b/x/committee/types/committee.go
index 86574e88..aa32f6a8 100644
--- a/x/committee/types/committee.go
+++ b/x/committee/types/committee.go
@@ -381,7 +381,7 @@ func MustNewProposal(pubProposal PubProposal, id uint64, committeeID uint64, dea
return proposal
}
-// GetPubProposal returns the PubProposal (govtypes.Content)
+// GetContent returns the PubProposal (govtypes.Content)
func (p Proposal) GetContent() PubProposal {
content, ok := p.Content.GetCachedValue().(PubProposal)
if !ok {
diff --git a/x/committee/types/genesis.go b/x/committee/types/genesis.go
index e34331b3..11d4bdc4 100644
--- a/x/committee/types/genesis.go
+++ b/x/committee/types/genesis.go
@@ -7,7 +7,7 @@ import (
proto "github.com/cosmos/gogoproto/proto"
)
-// DefaultNextProposalID is the starting poiint for proposal IDs.
+// DefaultNextProposalID is the starting point for proposal IDs.
const DefaultNextProposalID uint64 = 1
// NewGenesisState returns a new genesis state object for the module.
diff --git a/x/evmutil/keeper/bank_keeper.go b/x/evmutil/keeper/bank_keeper.go
deleted file mode 100644
index c21fd83b..00000000
--- a/x/evmutil/keeper/bank_keeper.go
+++ /dev/null
@@ -1,282 +0,0 @@
-package keeper
-
-import (
- "fmt"
-
- errorsmod "cosmossdk.io/errors"
- sdkmath "cosmossdk.io/math"
- sdk "github.com/cosmos/cosmos-sdk/types"
- sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
- evmtypes "github.com/evmos/ethermint/x/evm/types"
-
- "github.com/0glabs/0g-chain/chaincfg"
- "github.com/0glabs/0g-chain/x/evmutil/types"
-)
-
-// ConversionMultiplier is the conversion multiplier between evm denom and gas denom
-var ConversionMultiplier = sdkmath.NewInt(chaincfg.GasDenomConversionMultiplier)
-
-var _ evmtypes.BankKeeper = EvmBankKeeper{}
-
-// EvmBankKeeper is a BankKeeper wrapper for the x/evm module to allow the use
-// of the 18 decimal evm denom coin on the evm.
-// x/evm consumes gas and send coins by minting and burning evm denom coins in its module
-// account and then sending the funds to the target account.
-// This keeper uses both the gas denom coin and a separate evm denom balance to manage the
-// extra percision needed by the evm.
-type EvmBankKeeper struct {
- evmDenomKeeper Keeper
- bk types.BankKeeper
- ak types.AccountKeeper
-}
-
-func NewEvmBankKeeper(baseKeeper Keeper, bk types.BankKeeper, ak types.AccountKeeper) EvmBankKeeper {
- return EvmBankKeeper{
- evmDenomKeeper: baseKeeper,
- bk: bk,
- ak: ak,
- }
-}
-
-// GetBalance returns the total **spendable** balance of evm denom for a given account by address.
-func (k EvmBankKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin {
- if denom != chaincfg.EvmDenom {
- panic(fmt.Errorf("only evm denom %s is supported by EvmBankKeeper", chaincfg.EvmDenom))
- }
-
- spendableCoins := k.bk.SpendableCoins(ctx, addr)
- gasDenomFromBank := spendableCoins.AmountOf(chaincfg.GasDenom)
- evmDenomFromBank := spendableCoins.AmountOf(chaincfg.EvmDenom)
- evmDenomFromEvmBank := k.evmDenomKeeper.GetBalance(ctx, addr)
-
- var total sdkmath.Int
-
- if gasDenomFromBank.IsPositive() {
- total = gasDenomFromBank.Mul(ConversionMultiplier).Add(evmDenomFromBank).Add(evmDenomFromEvmBank)
- } else {
- total = evmDenomFromBank.Add(evmDenomFromEvmBank)
- }
- return sdk.NewCoin(chaincfg.EvmDenom, total)
-}
-
-// SendCoins transfers evm denom coins from a AccAddress to an AccAddress.
-func (k EvmBankKeeper) SendCoins(ctx sdk.Context, senderAddr sdk.AccAddress, recipientAddr sdk.AccAddress, amt sdk.Coins) error {
- // SendCoins method is not used by the evm module, but is required by the
- // evmtypes.BankKeeper interface. This must be updated if the evm module
- // is updated to use SendCoins.
- panic("not implemented")
-}
-
-// SendCoinsFromModuleToAccount transfers evm denom coins from a ModuleAccount to an AccAddress.
-// It will panic if the module account does not exist. An error is returned if the recipient
-// address is black-listed or if sending the tokens fails.
-func (k EvmBankKeeper) SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error {
- gasDenomCoin, baseDemonCnt, err := SplitEvmDenomCoins(amt)
- if err != nil {
- return err
- }
-
- if gasDenomCoin.Amount.IsPositive() {
- if err := k.bk.SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, sdk.NewCoins(gasDenomCoin)); err != nil {
- return err
- }
- }
-
- senderAddr := k.GetModuleAddress(senderModule)
- if err := k.ConvertOneGasDenomToEvmDenomIfNeeded(ctx, senderAddr, baseDemonCnt); err != nil {
- return err
- }
-
- if err := k.evmDenomKeeper.SendBalance(ctx, senderAddr, recipientAddr, baseDemonCnt); err != nil {
- return err
- }
-
- return k.ConvertEvmDenomToGasDenom(ctx, recipientAddr)
-}
-
-// SendCoinsFromAccountToModule transfers evm denom coins from an AccAddress to a ModuleAccount.
-// It will panic if the module account does not exist.
-func (k EvmBankKeeper) SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error {
- gasDenomCoin, evmDenomCnt, err := SplitEvmDenomCoins(amt)
- if err != nil {
- return err
- }
-
- if gasDenomCoin.IsPositive() {
- if err := k.bk.SendCoinsFromAccountToModule(ctx, senderAddr, recipientModule, sdk.NewCoins(gasDenomCoin)); err != nil {
- return err
- }
- }
-
- if err := k.ConvertOneGasDenomToEvmDenomIfNeeded(ctx, senderAddr, evmDenomCnt); err != nil {
- return err
- }
-
- recipientAddr := k.GetModuleAddress(recipientModule)
- if err := k.evmDenomKeeper.SendBalance(ctx, senderAddr, recipientAddr, evmDenomCnt); err != nil {
- return err
- }
-
- return k.ConvertEvmDenomToGasDenom(ctx, recipientAddr)
-}
-
-// MintCoins mints evm denom coins by minting the equivalent gas denom coins and any remaining evm denom coins.
-// It will panic if the module account does not exist or is unauthorized.
-func (k EvmBankKeeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error {
- gasDenomCoin, baseDemonCnt, err := SplitEvmDenomCoins(amt)
- if err != nil {
- return err
- }
-
- if gasDenomCoin.IsPositive() {
- if err := k.bk.MintCoins(ctx, moduleName, sdk.NewCoins(gasDenomCoin)); err != nil {
- return err
- }
- }
-
- recipientAddr := k.GetModuleAddress(moduleName)
- if err := k.evmDenomKeeper.AddBalance(ctx, recipientAddr, baseDemonCnt); err != nil {
- return err
- }
-
- return k.ConvertEvmDenomToGasDenom(ctx, recipientAddr)
-}
-
-// BurnCoins burns evm denom coins by burning the equivalent gas denom coins and any remaining evm denom coins.
-// It will panic if the module account does not exist or is unauthorized.
-func (k EvmBankKeeper) BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error {
- gasDenomCoin, baseDemonCnt, err := SplitEvmDenomCoins(amt)
- if err != nil {
- return err
- }
-
- if gasDenomCoin.IsPositive() {
- if err := k.bk.BurnCoins(ctx, moduleName, sdk.NewCoins(gasDenomCoin)); err != nil {
- return err
- }
- }
-
- moduleAddr := k.GetModuleAddress(moduleName)
- if err := k.ConvertOneGasDenomToEvmDenomIfNeeded(ctx, moduleAddr, baseDemonCnt); err != nil {
- return err
- }
-
- return k.evmDenomKeeper.RemoveBalance(ctx, moduleAddr, baseDemonCnt)
-}
-
-// IsSendEnabledCoins checks the coins provided and returns an ErrSendDisabled
-// if any of the coins are not configured for sending. Returns nil if sending is
-// enabled for all provided coins.
-func (k EvmBankKeeper) IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error {
- // IsSendEnabledCoins method is not used by the evm module, but is required by the
- // evmtypes.BankKeeper interface. This must be updated if the evm module
- // is updated to use IsSendEnabledCoins.
- panic("not implemented")
-}
-
-// ConvertOnegasDenomToEvmDenomIfNeeded converts 1 gas denom to evm denom for an address if
-// its evm denom balance is smaller than the evmDenomCnt amount.
-func (k EvmBankKeeper) ConvertOneGasDenomToEvmDenomIfNeeded(ctx sdk.Context, addr sdk.AccAddress, evmDenomCnt sdkmath.Int) error {
- evmDenomBal := k.evmDenomKeeper.GetBalance(ctx, addr)
- if evmDenomBal.GTE(evmDenomCnt) {
- return nil
- }
-
- gasDenomToStore := sdk.NewCoins(sdk.NewCoin(chaincfg.GasDenom, sdk.OneInt()))
- if err := k.bk.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, gasDenomToStore); err != nil {
- return err
- }
-
- // add 1 gas denom equivalent of evm denom to addr
- evmDenomToReceive := ConversionMultiplier
- if err := k.evmDenomKeeper.AddBalance(ctx, addr, evmDenomToReceive); err != nil {
- return err
- }
-
- return nil
-}
-
-// ConvertEvmDenomTogasDenom converts all available evm denom to gas denom for a given AccAddress.
-func (k EvmBankKeeper) ConvertEvmDenomToGasDenom(ctx sdk.Context, addr sdk.AccAddress) error {
- totalEvmDenom := k.evmDenomKeeper.GetBalance(ctx, addr)
- gasDenomCoin, _, err := SplitEvmDenomCoins(sdk.NewCoins(sdk.NewCoin(chaincfg.EvmDenom, totalEvmDenom)))
- if err != nil {
- return err
- }
-
- // do nothing if account does not have enough evm denom for a single gas denom
- gasDenomToReceive := gasDenomCoin.Amount
- if !gasDenomToReceive.IsPositive() {
- return nil
- }
-
- // remove evm denom used for converting to gas denom
- evmDenomToBurn := gasDenomToReceive.Mul(ConversionMultiplier)
- finalBal := totalEvmDenom.Sub(evmDenomToBurn)
- if err := k.evmDenomKeeper.SetBalance(ctx, addr, finalBal); err != nil {
- return err
- }
-
- fromAddr := k.GetModuleAddress(types.ModuleName)
- if err := k.bk.SendCoins(ctx, fromAddr, addr, sdk.NewCoins(gasDenomCoin)); err != nil {
- return err
- }
-
- return nil
-}
-
-func (k EvmBankKeeper) GetModuleAddress(moduleName string) sdk.AccAddress {
- addr := k.ak.GetModuleAddress(moduleName)
- if addr == nil {
- panic(errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName))
- }
- return addr
-}
-
-// SplitEvmDenomCoins splits evm denom coins to the equivalent gas denom coins and any remaining evm denom balance.
-// An error will be returned if the coins are not valid or if the coins are not the evm denom.
-func SplitEvmDenomCoins(coins sdk.Coins) (sdk.Coin, sdkmath.Int, error) {
- baseDemonCnt := sdk.ZeroInt()
- gasDenomAmt := sdk.NewCoin(chaincfg.GasDenom, sdk.ZeroInt())
-
- if len(coins) == 0 {
- return gasDenomAmt, baseDemonCnt, nil
- }
-
- if err := ValidateEvmCoins(coins); err != nil {
- return gasDenomAmt, baseDemonCnt, err
- }
-
- // note: we should always have len(coins) == 1 here since coins cannot have dup denoms after we validate.
- coin := coins[0]
- remainingBalance := coin.Amount.Mod(ConversionMultiplier)
- if remainingBalance.IsPositive() {
- baseDemonCnt = remainingBalance
- }
- gasDenomAmount := coin.Amount.Quo(ConversionMultiplier)
- if gasDenomAmount.IsPositive() {
- gasDenomAmt = sdk.NewCoin(chaincfg.GasDenom, gasDenomAmount)
- }
-
- return gasDenomAmt, baseDemonCnt, nil
-}
-
-// ValidateEvmCoins validates the coins from evm is valid and is the evm denom.
-func ValidateEvmCoins(coins sdk.Coins) error {
- if len(coins) == 0 {
- return nil
- }
-
- // validate that coins are non-negative, sorted, and no dup denoms
- if err := coins.Validate(); err != nil {
- return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, coins.String())
- }
-
- // validate that coin denom is evm denom
- if len(coins) != 1 || coins[0].Denom != chaincfg.EvmDenom {
- errMsg := fmt.Sprintf("invalid evm coin denom, only %s is supported", chaincfg.EvmDenom)
- return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, errMsg)
- }
-
- return nil
-}
diff --git a/x/evmutil/keeper/bank_keeper_test.go b/x/evmutil/keeper/bank_keeper_test.go
deleted file mode 100644
index 9fae9f68..00000000
--- a/x/evmutil/keeper/bank_keeper_test.go
+++ /dev/null
@@ -1,805 +0,0 @@
-package keeper_test
-
-import (
- "math/big"
- "testing"
- "time"
-
- sdkmath "cosmossdk.io/math"
- tmtime "github.com/cometbft/cometbft/types/time"
- sdk "github.com/cosmos/cosmos-sdk/types"
- "github.com/stretchr/testify/suite"
-
- authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
- vesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
- evmtypes "github.com/evmos/ethermint/x/evm/types"
-
- "github.com/0glabs/0g-chain/chaincfg"
- "github.com/0glabs/0g-chain/x/evmutil/keeper"
- "github.com/0glabs/0g-chain/x/evmutil/testutil"
- "github.com/0glabs/0g-chain/x/evmutil/types"
-)
-
-type evmBankKeeperTestSuite struct {
- testutil.Suite
-}
-
-func (suite *evmBankKeeperTestSuite) SetupTest() {
- suite.Suite.SetupTest()
-}
-
-func (suite *evmBankKeeperTestSuite) TestGetBalance_ReturnsSpendable() {
- startingCoins := sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 10))
- startingEvmDenom := sdkmath.NewInt(100)
-
- now := tmtime.Now()
- endTime := now.Add(24 * time.Hour)
- bacc := authtypes.NewBaseAccountWithAddress(suite.Addrs[0])
- vacc := vesting.NewContinuousVestingAccount(bacc, startingCoins, now.Unix(), endTime.Unix())
- suite.AccountKeeper.SetAccount(suite.Ctx, vacc)
-
- err := suite.App.FundAccount(suite.Ctx, suite.Addrs[0], startingCoins)
- suite.Require().NoError(err)
- err = suite.Keeper.SetBalance(suite.Ctx, suite.Addrs[0], startingEvmDenom)
- suite.Require().NoError(err)
-
- coin := suite.EvmBankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], chaincfg.EvmDenom)
- suite.Require().Equal(startingEvmDenom, coin.Amount)
-
- ctx := suite.Ctx.WithBlockTime(now.Add(12 * time.Hour))
- coin = suite.EvmBankKeeper.GetBalance(ctx, suite.Addrs[0], chaincfg.EvmDenom)
- suite.Require().Equal(sdkmath.NewIntFromUint64(5_000_000_000_100), coin.Amount)
-}
-
-func (suite *evmBankKeeperTestSuite) TestGetBalance_NotEvmDenom() {
- suite.Require().Panics(func() {
- suite.EvmBankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], chaincfg.GasDenom)
- })
- suite.Require().Panics(func() {
- suite.EvmBankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], "busd")
- })
-}
-
-func (suite *evmBankKeeperTestSuite) TestGetBalance() {
- tests := []struct {
- name string
- startingAmount sdk.Coins
- expAmount sdkmath.Int
- }{
- {
- "gas denom with evm denom",
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 100),
- sdk.NewInt64Coin(chaincfg.GasDenom, 10),
- ),
- sdkmath.NewInt(10_000_000_000_100),
- },
- {
- "just evm denom",
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 100),
- sdk.NewInt64Coin("busd", 100),
- ),
- sdkmath.NewInt(100),
- },
- {
- "just gas denom",
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.GasDenom, 10),
- sdk.NewInt64Coin("busd", 100),
- ),
- sdkmath.NewInt(10_000_000_000_000),
- },
- {
- "no gas denom or evm denom",
- sdk.NewCoins(),
- sdk.ZeroInt(),
- },
- {
- "with avaka that is more than 1 gas denom",
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 20_000_000_000_220),
- sdk.NewInt64Coin(chaincfg.GasDenom, 11),
- ),
- sdkmath.NewInt(31_000_000_000_220),
- },
- }
-
- for _, tt := range tests {
- suite.Run(tt.name, func() {
- suite.SetupTest()
-
- suite.FundAccountWithZgChain(suite.Addrs[0], tt.startingAmount)
- coin := suite.EvmBankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], chaincfg.EvmDenom)
- suite.Require().Equal(tt.expAmount, coin.Amount)
- })
- }
-}
-
-func (suite *evmBankKeeperTestSuite) TestSendCoinsFromModuleToAccount() {
- startingModuleCoins := sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 200),
- sdk.NewInt64Coin(chaincfg.GasDenom, 100),
- )
- tests := []struct {
- name string
- sendCoins sdk.Coins
- startingAccBal sdk.Coins
- expAccBal sdk.Coins
- hasErr bool
- }{
- {
- "send more than 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 12_000_000_000_010)),
- sdk.Coins{},
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 10),
- sdk.NewInt64Coin(chaincfg.GasDenom, 12),
- ),
- false,
- },
- {
- "send less than 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 122)),
- sdk.Coins{},
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 122),
- sdk.NewInt64Coin(chaincfg.GasDenom, 0),
- ),
- false,
- },
- {
- "send an exact amount of gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 98_000_000_000_000)),
- sdk.Coins{},
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 0o0),
- sdk.NewInt64Coin(chaincfg.GasDenom, 98),
- ),
- false,
- },
- {
- "send no evm denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 0)),
- sdk.Coins{},
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 0),
- sdk.NewInt64Coin(chaincfg.GasDenom, 0),
- ),
- false,
- },
- {
- "errors if sending other coins",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 500), sdk.NewInt64Coin("busd", 1000)),
- sdk.Coins{},
- sdk.Coins{},
- true,
- },
- {
- "errors if not enough total evm denom to cover",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100_000_000_001_000)),
- sdk.Coins{},
- sdk.Coins{},
- true,
- },
- {
- "errors if not enough gas denom to cover",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 200_000_000_000_000)),
- sdk.Coins{},
- sdk.Coins{},
- true,
- },
- {
- "converts receiver's evm denom to gas denom if there's enough evm denom after the transfer",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 99_000_000_000_200)),
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 999_999_999_900),
- sdk.NewInt64Coin(chaincfg.GasDenom, 1),
- ),
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 100),
- sdk.NewInt64Coin(chaincfg.GasDenom, 101),
- ),
- false,
- },
- {
- "converts all of receiver's evm denom to gas denom even if somehow receiver has more than 1 gas denom of evm denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 12_000_000_000_100)),
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 5_999_999_999_990),
- sdk.NewInt64Coin(chaincfg.GasDenom, 1),
- ),
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 90),
- sdk.NewInt64Coin(chaincfg.GasDenom, 19),
- ),
- false,
- },
- {
- "swap 1 gas denom for evm denom if module account doesn't have enough evm denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 99_000_000_001_000)),
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 200),
- sdk.NewInt64Coin(chaincfg.GasDenom, 1),
- ),
- sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 1200),
- sdk.NewInt64Coin(chaincfg.GasDenom, 100),
- ),
- false,
- },
- }
-
- for _, tt := range tests {
- suite.Run(tt.name, func() {
- suite.SetupTest()
-
- suite.FundAccountWithZgChain(suite.Addrs[0], tt.startingAccBal)
- suite.FundModuleAccountWithZgChain(evmtypes.ModuleName, startingModuleCoins)
-
- // fund our module with some gas denom to account for converting extra evm denom back to gas denom
- suite.FundModuleAccountWithZgChain(types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 10)))
-
- err := suite.EvmBankKeeper.SendCoinsFromModuleToAccount(suite.Ctx, evmtypes.ModuleName, suite.Addrs[0], tt.sendCoins)
- if tt.hasErr {
- suite.Require().Error(err)
- return
- } else {
- suite.Require().NoError(err)
- }
-
- // check gas denom
- GasDenomSender := suite.BankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], chaincfg.GasDenom)
- suite.Require().Equal(tt.expAccBal.AmountOf(chaincfg.GasDenom).Int64(), GasDenomSender.Amount.Int64())
-
- // check evm denom
- actualEvmDenom := suite.Keeper.GetBalance(suite.Ctx, suite.Addrs[0])
- suite.Require().Equal(tt.expAccBal.AmountOf(chaincfg.EvmDenom).Int64(), actualEvmDenom.Int64())
- })
- }
-}
-
-func (suite *evmBankKeeperTestSuite) TestSendCoinsFromAccountToModule() {
- startingAccCoins := sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 200),
- sdk.NewInt64Coin(chaincfg.GasDenom, 100),
- )
- startingModuleCoins := sdk.NewCoins(
- sdk.NewInt64Coin(chaincfg.EvmDenom, 100_000_000_000),
- )
- tests := []struct {
- name string
- sendCoins sdk.Coins
- expSenderCoins sdk.Coins
- expModuleCoins sdk.Coins
- hasErr bool
- }{
- {
- "send more than 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 12_000_000_000_010)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 190), sdk.NewInt64Coin(chaincfg.GasDenom, 88)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100_000_000_010), sdk.NewInt64Coin(chaincfg.GasDenom, 12)),
- false,
- },
- {
- "send less than 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 122)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 78), sdk.NewInt64Coin(chaincfg.GasDenom, 100)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100_000_000_122), sdk.NewInt64Coin(chaincfg.GasDenom, 0)),
- false,
- },
- {
- "send an exact amount of gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 98_000_000_000_000)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 200), sdk.NewInt64Coin(chaincfg.GasDenom, 2)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100_000_000_000), sdk.NewInt64Coin(chaincfg.GasDenom, 98)),
- false,
- },
- {
- "send no evm denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 0)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 200), sdk.NewInt64Coin(chaincfg.GasDenom, 100)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100_000_000_000), sdk.NewInt64Coin(chaincfg.GasDenom, 0)),
- false,
- },
- {
- "errors if sending other coins",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 500), sdk.NewInt64Coin("busd", 1000)),
- sdk.Coins{},
- sdk.Coins{},
- true,
- },
- {
- "errors if have dup coins",
- sdk.Coins{
- sdk.NewInt64Coin(chaincfg.EvmDenom, 12_000_000_000_000),
- sdk.NewInt64Coin(chaincfg.EvmDenom, 2_000_000_000_000),
- },
- sdk.Coins{},
- sdk.Coins{},
- true,
- },
- {
- "errors if not enough total evm denom to cover",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100_000_000_001_000)),
- sdk.Coins{},
- sdk.Coins{},
- true,
- },
- {
- "errors if not enough gas denom to cover",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 200_000_000_000_000)),
- sdk.Coins{},
- sdk.Coins{},
- true,
- },
- {
- "converts 1 gas denom to evm denom if not enough evm denom to cover",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 99_001_000_000_000)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 999_000_000_200), sdk.NewInt64Coin(chaincfg.GasDenom, 0)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 101_000_000_000), sdk.NewInt64Coin(chaincfg.GasDenom, 99)),
- false,
- },
- {
- "converts receiver's evm denom to gas denom if there's enough evm denom after the transfer",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 5_900_000_000_200)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100_000_000_000), sdk.NewInt64Coin(chaincfg.GasDenom, 94)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 200), sdk.NewInt64Coin(chaincfg.GasDenom, 6)),
- false,
- },
- }
-
- for _, tt := range tests {
- suite.Run(tt.name, func() {
- suite.SetupTest()
- suite.FundAccountWithZgChain(suite.Addrs[0], startingAccCoins)
- suite.FundModuleAccountWithZgChain(evmtypes.ModuleName, startingModuleCoins)
-
- err := suite.EvmBankKeeper.SendCoinsFromAccountToModule(suite.Ctx, suite.Addrs[0], evmtypes.ModuleName, tt.sendCoins)
- if tt.hasErr {
- suite.Require().Error(err)
- return
- } else {
- suite.Require().NoError(err)
- }
-
- // check sender balance
- GasDenomSender := suite.BankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], chaincfg.GasDenom)
- suite.Require().Equal(tt.expSenderCoins.AmountOf(chaincfg.GasDenom).Int64(), GasDenomSender.Amount.Int64())
- actualEvmDenom := suite.Keeper.GetBalance(suite.Ctx, suite.Addrs[0])
- suite.Require().Equal(tt.expSenderCoins.AmountOf(chaincfg.EvmDenom).Int64(), actualEvmDenom.Int64())
-
- // check module balance
- moduleAddr := suite.AccountKeeper.GetModuleAddress(evmtypes.ModuleName)
- GasDenomSender = suite.BankKeeper.GetBalance(suite.Ctx, moduleAddr, chaincfg.GasDenom)
- suite.Require().Equal(tt.expModuleCoins.AmountOf(chaincfg.GasDenom).Int64(), GasDenomSender.Amount.Int64())
- actualEvmDenom = suite.Keeper.GetBalance(suite.Ctx, moduleAddr)
- suite.Require().Equal(tt.expModuleCoins.AmountOf(chaincfg.EvmDenom).Int64(), actualEvmDenom.Int64())
- })
- }
-}
-
-func (suite *evmBankKeeperTestSuite) TestBurnCoins() {
- startingGasDenom := sdkmath.NewInt(100)
- tests := []struct {
- name string
- burnCoins sdk.Coins
- expGasDenom sdkmath.Int
- expEvmDenom sdkmath.Int
- hasErr bool
- evmDenomStart sdkmath.Int
- }{
- {
- "burn more than 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 12_021_000_000_002)),
- sdkmath.NewInt(88),
- sdkmath.NewInt(100_000_000_000),
- false,
- sdkmath.NewInt(121_000_000_002),
- },
- {
- "burn less than 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 122)),
- sdkmath.NewInt(100),
- sdkmath.NewInt(878),
- false,
- sdkmath.NewInt(1000),
- },
- {
- "burn an exact amount of gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 98_000_000_000_000)),
- sdkmath.NewInt(2),
- sdkmath.NewInt(10),
- false,
- sdkmath.NewInt(10),
- },
- {
- "burn no evm denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 0)),
- startingGasDenom,
- sdk.ZeroInt(),
- false,
- sdk.ZeroInt(),
- },
- {
- "errors if burning other coins",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 500), sdk.NewInt64Coin("busd", 1000)),
- startingGasDenom,
- sdkmath.NewInt(100),
- true,
- sdkmath.NewInt(100),
- },
- {
- "errors if have dup coins",
- sdk.Coins{
- sdk.NewInt64Coin(chaincfg.EvmDenom, 12_000_000_000_000),
- sdk.NewInt64Coin(chaincfg.EvmDenom, 2_000_000_000_000),
- },
- startingGasDenom,
- sdk.ZeroInt(),
- true,
- sdk.ZeroInt(),
- },
- {
- "errors if burn amount is negative",
- sdk.Coins{sdk.Coin{Denom: chaincfg.EvmDenom, Amount: sdkmath.NewInt(-100)}},
- startingGasDenom,
- sdkmath.NewInt(50),
- true,
- sdkmath.NewInt(50),
- },
- {
- "errors if not enough evm denom to cover burn",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100_999_000_000_000)),
- sdkmath.NewInt(0),
- sdkmath.NewInt(99_000_000_000),
- true,
- sdkmath.NewInt(99_000_000_000),
- },
- {
- "errors if not enough gas denom to cover burn",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 200_000_000_000_000)),
- sdkmath.NewInt(100),
- sdk.ZeroInt(),
- true,
- sdk.ZeroInt(),
- },
- {
- "converts 1 gas denom to evm denom if not enough evm denom to cover",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 12_021_000_000_002)),
- sdkmath.NewInt(87),
- sdkmath.NewInt(980_000_000_000),
- false,
- sdkmath.NewInt(1_000_000_002),
- },
- }
-
- for _, tt := range tests {
- suite.Run(tt.name, func() {
- suite.SetupTest()
- startingCoins := sdk.NewCoins(
- sdk.NewCoin(chaincfg.GasDenom, startingGasDenom),
- sdk.NewCoin(chaincfg.EvmDenom, tt.evmDenomStart),
- )
- suite.FundModuleAccountWithZgChain(evmtypes.ModuleName, startingCoins)
-
- err := suite.EvmBankKeeper.BurnCoins(suite.Ctx, evmtypes.ModuleName, tt.burnCoins)
- if tt.hasErr {
- suite.Require().Error(err)
- return
- } else {
- suite.Require().NoError(err)
- }
-
- // check gas denom
- GasDenomActual := suite.BankKeeper.GetBalance(suite.Ctx, suite.EvmModuleAddr, chaincfg.GasDenom)
- suite.Require().Equal(tt.expGasDenom, GasDenomActual.Amount)
-
- // check evm denom
- evmDenomActual := suite.Keeper.GetBalance(suite.Ctx, suite.EvmModuleAddr)
- suite.Require().Equal(tt.expEvmDenom, evmDenomActual)
- })
- }
-}
-
-func (suite *evmBankKeeperTestSuite) TestMintCoins() {
- tests := []struct {
- name string
- mintCoins sdk.Coins
- GasDenomCnt sdkmath.Int
- evmDenomCnt sdkmath.Int
- hasErr bool
- evmDenomStart sdkmath.Int
- }{
- {
- "mint more than 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 12_021_000_000_002)),
- sdkmath.NewInt(12),
- sdkmath.NewInt(21_000_000_002),
- false,
- sdk.ZeroInt(),
- },
- {
- "mint less than 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 901_000_000_001)),
- sdk.ZeroInt(),
- sdkmath.NewInt(901_000_000_001),
- false,
- sdk.ZeroInt(),
- },
- {
- "mint an exact amount of gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 123_000_000_000_000_000)),
- sdkmath.NewInt(123_000),
- sdk.ZeroInt(),
- false,
- sdk.ZeroInt(),
- },
- {
- "mint no evm denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 0)),
- sdk.ZeroInt(),
- sdk.ZeroInt(),
- false,
- sdk.ZeroInt(),
- },
- {
- "errors if minting other coins",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 500), sdk.NewInt64Coin("busd", 1000)),
- sdk.ZeroInt(),
- sdkmath.NewInt(100),
- true,
- sdkmath.NewInt(100),
- },
- {
- "errors if have dup coins",
- sdk.Coins{
- sdk.NewInt64Coin(chaincfg.EvmDenom, 12_000_000_000_000),
- sdk.NewInt64Coin(chaincfg.EvmDenom, 2_000_000_000_000),
- },
- sdk.ZeroInt(),
- sdk.ZeroInt(),
- true,
- sdk.ZeroInt(),
- },
- {
- "errors if mint amount is negative",
- sdk.Coins{sdk.Coin{Denom: chaincfg.EvmDenom, Amount: sdkmath.NewInt(-100)}},
- sdk.ZeroInt(),
- sdkmath.NewInt(50),
- true,
- sdkmath.NewInt(50),
- },
- {
- "adds to existing evm denom balance",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 12_021_000_000_002)),
- sdkmath.NewInt(12),
- sdkmath.NewInt(21_000_000_102),
- false,
- sdkmath.NewInt(100),
- },
- {
- "convert evm denom balance to gas denom if it exceeds 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 10_999_000_000_000)),
- sdkmath.NewInt(12),
- sdkmath.NewInt(1_200_000_001),
- false,
- sdkmath.NewInt(1_002_200_000_001),
- },
- }
-
- for _, tt := range tests {
- suite.Run(tt.name, func() {
- suite.SetupTest()
- suite.FundModuleAccountWithZgChain(types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 10)))
- suite.FundModuleAccountWithZgChain(evmtypes.ModuleName, sdk.NewCoins(sdk.NewCoin(chaincfg.EvmDenom, tt.evmDenomStart)))
-
- err := suite.EvmBankKeeper.MintCoins(suite.Ctx, evmtypes.ModuleName, tt.mintCoins)
- if tt.hasErr {
- suite.Require().Error(err)
- return
- } else {
- suite.Require().NoError(err)
- }
-
- // check gas denom
- GasDenomActual := suite.BankKeeper.GetBalance(suite.Ctx, suite.EvmModuleAddr, chaincfg.GasDenom)
- suite.Require().Equal(tt.GasDenomCnt, GasDenomActual.Amount)
-
- // check evm denom
- evmDenomActual := suite.Keeper.GetBalance(suite.Ctx, suite.EvmModuleAddr)
- suite.Require().Equal(tt.evmDenomCnt, evmDenomActual)
- })
- }
-}
-
-func (suite *evmBankKeeperTestSuite) TestValidateEvmCoins() {
- tests := []struct {
- name string
- coins sdk.Coins
- shouldErr bool
- }{
- {
- "valid coins",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 500)),
- false,
- },
- {
- "dup coins",
- sdk.Coins{sdk.NewInt64Coin(chaincfg.EvmDenom, 500), sdk.NewInt64Coin(chaincfg.EvmDenom, 500)},
- true,
- },
- {
- "not evm coins",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 500)),
- true,
- },
- {
- "negative coins",
- sdk.Coins{sdk.Coin{Denom: chaincfg.EvmDenom, Amount: sdkmath.NewInt(-500)}},
- true,
- },
- }
- for _, tt := range tests {
- suite.Run(tt.name, func() {
- err := keeper.ValidateEvmCoins(tt.coins)
- if tt.shouldErr {
- suite.Require().Error(err)
- } else {
- suite.Require().NoError(err)
- }
- })
- }
-}
-
-func (suite *evmBankKeeperTestSuite) TestConvertOneGasDenomToEvmDenomIfNeeded() {
- evmDenomNeeded := sdkmath.NewInt(200)
- tests := []struct {
- name string
- startingCoins sdk.Coins
- expectedCoins sdk.Coins
- success bool
- }{
- {
- "not enough gas denom for conversion",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100)),
- false,
- },
- {
- "converts 1 gas denom to evm denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 10), sdk.NewInt64Coin(chaincfg.EvmDenom, 100)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 9), sdk.NewInt64Coin(chaincfg.EvmDenom, 1_000_000_000_100)),
- true,
- },
- {
- "conversion not needed",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 10), sdk.NewInt64Coin(chaincfg.EvmDenom, 200)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 10), sdk.NewInt64Coin(chaincfg.EvmDenom, 200)),
- true,
- },
- }
- for _, tt := range tests {
- suite.Run(tt.name, func() {
- suite.SetupTest()
-
- suite.FundAccountWithZgChain(suite.Addrs[0], tt.startingCoins)
- err := suite.EvmBankKeeper.ConvertOneGasDenomToEvmDenomIfNeeded(suite.Ctx, suite.Addrs[0], evmDenomNeeded)
- moduleZgChain := suite.BankKeeper.GetBalance(suite.Ctx, suite.AccountKeeper.GetModuleAddress(types.ModuleName), chaincfg.GasDenom)
- if tt.success {
- suite.Require().NoError(err)
- if tt.startingCoins.AmountOf(chaincfg.EvmDenom).LT(evmDenomNeeded) {
- suite.Require().Equal(sdk.OneInt(), moduleZgChain.Amount)
- }
- } else {
- suite.Require().Error(err)
- suite.Require().Equal(sdk.ZeroInt(), moduleZgChain.Amount)
- }
-
- evmDenomCnt := suite.Keeper.GetBalance(suite.Ctx, suite.Addrs[0])
- suite.Require().Equal(tt.expectedCoins.AmountOf(chaincfg.EvmDenom), evmDenomCnt)
- GasDenomCoin := suite.BankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], chaincfg.GasDenom)
- suite.Require().Equal(tt.expectedCoins.AmountOf(chaincfg.GasDenom), GasDenomCoin.Amount)
- })
- }
-}
-
-func (suite *evmBankKeeperTestSuite) TestConvertEvmDenomToGasDenom() {
- tests := []struct {
- name string
- startingCoins sdk.Coins
- expectedCoins sdk.Coins
- }{
- {
- "not enough gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 100), sdk.NewInt64Coin(chaincfg.GasDenom, 0)),
- },
- {
- "converts evm denom for 1 gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 10), sdk.NewInt64Coin(chaincfg.EvmDenom, 1_000_000_000_003)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 11), sdk.NewInt64Coin(chaincfg.EvmDenom, 3)),
- },
- {
- "converts more than 1 gas denom of evm denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 10), sdk.NewInt64Coin(chaincfg.EvmDenom, 8_000_000_000_123)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 18), sdk.NewInt64Coin(chaincfg.EvmDenom, 123)),
- },
- }
- for _, tt := range tests {
- suite.Run(tt.name, func() {
- suite.SetupTest()
-
- err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 10)))
- suite.Require().NoError(err)
- suite.FundAccountWithZgChain(suite.Addrs[0], tt.startingCoins)
- err = suite.EvmBankKeeper.ConvertEvmDenomToGasDenom(suite.Ctx, suite.Addrs[0])
- suite.Require().NoError(err)
- evmDenomCnt := suite.Keeper.GetBalance(suite.Ctx, suite.Addrs[0])
- suite.Require().Equal(tt.expectedCoins.AmountOf(chaincfg.EvmDenom), evmDenomCnt)
- GasDenomCoin := suite.BankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], chaincfg.GasDenom)
- suite.Require().Equal(tt.expectedCoins.AmountOf(chaincfg.GasDenom), GasDenomCoin.Amount)
- })
- }
-}
-
-func (suite *evmBankKeeperTestSuite) TestSplitEvmDenomCoins() {
- tests := []struct {
- name string
- coins sdk.Coins
- expectedCoins sdk.Coins
- shouldErr bool
- }{
- {
- "invalid coins",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 500)),
- nil,
- true,
- },
- {
- "empty coins",
- sdk.NewCoins(),
- sdk.NewCoins(),
- false,
- },
- {
- "gas denom & evm denom coins",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 8_000_000_000_123)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 8), sdk.NewInt64Coin(chaincfg.EvmDenom, 123)),
- false,
- },
- {
- "only evm denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 10_123)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 10_123)),
- false,
- },
- {
- "only gas denom",
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.EvmDenom, 5_000_000_000_000)),
- sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 5)),
- false,
- },
- }
- for _, tt := range tests {
- suite.Run(tt.name, func() {
- GasDenomCoin, evmDenomCnt, err := keeper.SplitEvmDenomCoins(tt.coins)
- if tt.shouldErr {
- suite.Require().Error(err)
- } else {
- suite.Require().NoError(err)
- suite.Require().Equal(tt.expectedCoins.AmountOf(chaincfg.GasDenom), GasDenomCoin.Amount)
- suite.Require().Equal(tt.expectedCoins.AmountOf(chaincfg.EvmDenom), evmDenomCnt)
- }
- })
- }
-}
-
-func TestEvmBankKeeperTestSuite(t *testing.T) {
- suite.Run(t, new(evmBankKeeperTestSuite))
-}
-
-func makeBigIntByString(s string) *big.Int {
- i, _ := new(big.Int).SetString(s, 10)
- return i
-}
diff --git a/x/evmutil/keeper/invariants.go b/x/evmutil/keeper/invariants.go
index afbfbbf5..26d9a533 100644
--- a/x/evmutil/keeper/invariants.go
+++ b/x/evmutil/keeper/invariants.go
@@ -6,14 +6,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
- "github.com/0glabs/0g-chain/chaincfg"
"github.com/0glabs/0g-chain/x/evmutil/types"
)
// RegisterInvariants registers the swap module invariants
func RegisterInvariants(ir sdk.InvariantRegistry, bankK types.BankKeeper, k Keeper) {
- ir.RegisterRoute(types.ModuleName, "fully-backed", FullyBackedInvariant(bankK, k))
- ir.RegisterRoute(types.ModuleName, "small-balances", SmallBalancesInvariant(bankK, k))
ir.RegisterRoute(types.ModuleName, "cosmos-coins-fully-backed", CosmosCoinsFullyBackedInvariant(bankK, k))
// Disable this invariant due to some issues with it requiring some staking params to be set in genesis.
// ir.RegisterRoute(types.ModuleName, "backed-conversion-coins", BackedCoinsInvariant(bankK, k))
@@ -22,57 +19,11 @@ func RegisterInvariants(ir sdk.InvariantRegistry, bankK types.BankKeeper, k Keep
// AllInvariants runs all invariants of the swap module
func AllInvariants(bankK types.BankKeeper, k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
- if res, stop := FullyBackedInvariant(bankK, k)(ctx); stop {
- return res, stop
- }
if res, stop := BackedCoinsInvariant(bankK, k)(ctx); stop {
return res, stop
}
- if res, stop := CosmosCoinsFullyBackedInvariant(bankK, k)(ctx); stop {
- return res, stop
- }
- return SmallBalancesInvariant(bankK, k)(ctx)
- }
-}
-// FullyBackedInvariant ensures all minor balances are backed by the coins in the module account.
-//
-// The module balance can be greater than the sum of all minor balances. This can happen in rare cases
-// where the evm module burns tokens.
-func FullyBackedInvariant(bankK types.BankKeeper, k Keeper) sdk.Invariant {
- broken := false
- message := sdk.FormatInvariant(types.ModuleName, "fully backed broken", "sum of minor balances greater than module account")
-
- return func(ctx sdk.Context) (string, bool) {
- totalMinorBalances := sdk.ZeroInt()
- k.IterateAllAccounts(ctx, func(acc types.Account) bool {
- totalMinorBalances = totalMinorBalances.Add(acc.Balance)
- return false
- })
-
- bankAddr := authtypes.NewModuleAddress(types.ModuleName)
- bankBalance := bankK.GetBalance(ctx, bankAddr, chaincfg.GasDenom).Amount.Mul(ConversionMultiplier)
-
- broken = totalMinorBalances.GT(bankBalance)
-
- return message, broken
- }
-}
-
-// SmallBalancesInvariant ensures all minor balances are less than the overflow amount, beyond this they should be converted to the major denom.
-func SmallBalancesInvariant(_ types.BankKeeper, k Keeper) sdk.Invariant {
- broken := false
- message := sdk.FormatInvariant(types.ModuleName, "small balances broken", "minor balances not all less than overflow")
-
- return func(ctx sdk.Context) (string, bool) {
- k.IterateAllAccounts(ctx, func(account types.Account) bool {
- if account.Balance.GTE(ConversionMultiplier) {
- broken = true
- return true
- }
- return false
- })
- return message, broken
+ return CosmosCoinsFullyBackedInvariant(bankK, k)(ctx)
}
}
diff --git a/x/evmutil/keeper/invariants_test.go b/x/evmutil/keeper/invariants_test.go
index 79fc4474..5a741ed4 100644
--- a/x/evmutil/keeper/invariants_test.go
+++ b/x/evmutil/keeper/invariants_test.go
@@ -44,7 +44,7 @@ func (suite *invariantTestSuite) SetupValidState() {
for i := 0; i < 4; i++ {
suite.Keeper.SetAccount(suite.Ctx, *types.NewAccount(
suite.Addrs[i],
- keeper.ConversionMultiplier.QuoRaw(2),
+ sdkmath.NewInt(10000000000),
))
}
suite.FundModuleAccountWithZgChain(
@@ -97,7 +97,7 @@ func (suite *invariantTestSuite) SetupValidState() {
suite.NoError(err)
}
-// RegisterRoutes implements sdk.InvariantRegistry
+// RegisterRoute implements sdk.InvariantRegistry
func (suite *invariantTestSuite) RegisterRoute(moduleName string, route string, invariant sdk.Invariant) {
_, exists := suite.invariants[moduleName]
@@ -132,45 +132,6 @@ func (suite *invariantTestSuite) runInvariant(route string, invariant func(bankK
return dMessage, dBroken
}
-func (suite *invariantTestSuite) TestFullyBackedInvariant() {
- // default state is valid
- _, broken := suite.runInvariant("fully-backed", keeper.FullyBackedInvariant)
- suite.Equal(false, broken)
-
- suite.SetupValidState()
- _, broken = suite.runInvariant("fully-backed", keeper.FullyBackedInvariant)
- suite.Equal(false, broken)
-
- // break invariant by increasing total minor balances above module balance
- suite.Keeper.AddBalance(suite.Ctx, suite.Addrs[0], sdk.OneInt())
-
- message, broken := suite.runInvariant("fully-backed", keeper.FullyBackedInvariant)
- suite.Equal("evmutil: fully backed broken invariant\nsum of minor balances greater than module account\n", message)
- suite.Equal(true, broken)
-}
-
-func (suite *invariantTestSuite) TestSmallBalances() {
- // default state is valid
- _, broken := suite.runInvariant("small-balances", keeper.SmallBalancesInvariant)
- suite.Equal(false, broken)
-
- suite.SetupValidState()
- _, broken = suite.runInvariant("small-balances", keeper.SmallBalancesInvariant)
- suite.Equal(false, broken)
-
- // increase minor balance at least above conversion multiplier
- suite.Keeper.AddBalance(suite.Ctx, suite.Addrs[0], keeper.ConversionMultiplier)
- // add same number of gas denom to avoid breaking other invariants
- amt := sdk.NewCoins(sdk.NewInt64Coin(chaincfg.GasDenom, 1))
- suite.Require().NoError(
- suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, amt),
- )
-
- message, broken := suite.runInvariant("small-balances", keeper.SmallBalancesInvariant)
- suite.Equal("evmutil: small balances broken invariant\nminor balances not all less than overflow\n", message)
- suite.Equal(true, broken)
-}
-
// the cosmos-coins-fully-backed invariant depends on 1-to-1 mapping of module balance to erc20s
// if coins can be sent directly to the module account, this assumption is broken.
// this test verifies that coins cannot be directly sent to the module account.
diff --git a/x/evmutil/spec/01_concepts.md b/x/evmutil/spec/01_concepts.md
index 7e3f9c82..1bfaf121 100644
--- a/x/evmutil/spec/01_concepts.md
+++ b/x/evmutil/spec/01_concepts.md
@@ -4,34 +4,6 @@ order: 1
# Concepts
-## EVM Gas Denom
-
-In order to use the EVM and be compatible with existing clients, the gas denom used by the EVM must be in 18 decimals. Since `ukava` has 6 decimals of precision, it cannot be used as the EVM gas denom directly.
-
-To use the Kava token on the EVM, the evmutil module provides an `EvmBankKeeper` that is responsible for the conversion of `ukava` and `akava`. A user's excess `akava` balance is stored in the `x/evmutil` store, while its `ukava` balance remains in the cosmos-sdk `x/bank` module.
-
-## `EvmBankKeeper` Overview
-
-The `EvmBankKeeper` provides access to an account's total `akava` balance and the ability to transfer, mint, and burn `akava`. If anything other than the `akava` denom is requested, the `EvmBankKeeper` will panic.
-
-This keeper implements the `x/evm` module's `BankKeeper` interface to enable the usage of `akava` denom on the EVM.
-
-### `x/evm` Parameter Requirements
-
-Since the EVM denom `akava` is required to use the `EvmBankKeeper`, it is necessary to set the `EVMDenom` param of the `x/evm` module to `akava`.
-
-### Balance Calculation of `akava`
-
-The `akava` balance of an account is derived from an account's **spendable** `ukava` balance times 10^12 (to derive its `akava` equivalent), plus the account's excess `akava` balance that can be accessed via the module `Keeper`.
-
-### `akava` <> `ukava` Conversion
-
-When an account does not have sufficient `akava` to cover a transfer or burn, the `EvmBankKeeper` will try to swap 1 `ukava` to its equivalent `akava` amount. It does this by transferring 1 `ukava` from the sender to the `x/evmutil` module account, then adding the equivalent `akava` amount to the sender's balance in the module state.
-
-In reverse, if an account has enough `akava` balance for one or more `ukava`, the excess `akava` balance will be converted to `ukava`. This is done by removing the excess `akava` balance in the module store, then transferring the equivalent `ukava` coins from the `x/evmutil` module account to the target account.
-
-The swap logic ensures that all `akava` is backed by the equivalent `ukava` balance stored in the module account.
-
## ERC20 token <> sdk.Coin Conversion
`x/evmutil` facilitates moving assets between Kava's EVM and Cosmos co-chains. This must be handled differently depending on which co-chain to which the asset it native. The messages controlling these flows involve two accounts:
@@ -46,7 +18,7 @@ When converting assets from the Cosmos co-chain to the EVM, the initiator is a `
`sdk.Coin`s native to the Cosmos co-chain can be converted to an ERC-20 representing the coin in the EVM. This works by transferring the coin from the initiator to `x/evmutil`'s module account and then minting an ERC-20 token to the receiver. Converting back, the initiator's ERC-20 representation of the coin is burned and the original Cosmos-native asset is transferred to the receiver.
-Cosmos-native asset converstion is done through the use of the `MsgConvertCosmosCoinToERC20` & `MsgConvertCosmosCoinFromERC20` messages (see **[Messages](03_messages.md)**).
+Cosmos-native asset conversion is done through the use of the `MsgConvertCosmosCoinToERC20` & `MsgConvertCosmosCoinFromERC20` messages (see **[Messages](03_messages.md)**).
Only Cosmos co-chain denominations that are in the `AllowedCosmosDenoms` param (see **[Params](05_params.md)**) can be converted via these messages.
@@ -65,7 +37,3 @@ EVM-native asset conversion is done through the use of the `MsgConvertERC20ToCoi
Only ERC20 contract address that are in the `EnabledConversionPairs` param (see **[Params](05_params.md)**) can be converted via these messages.
`EnabledConversionPairs` can be altered through governance.
-
-## Module Keeper
-
-The module Keeper provides access to an account's excess `akava` balance and the ability to update the balance.
diff --git a/x/evmutil/spec/02_state.md b/x/evmutil/spec/02_state.md
index 9d861d9c..763f4d61 100644
--- a/x/evmutil/spec/02_state.md
+++ b/x/evmutil/spec/02_state.md
@@ -51,24 +51,12 @@ message AllowedCosmosCoinERC20Token {
```protobuf
message GenesisState {
- repeated Account accounts = 1 [(gogoproto.nullable) = false];
+ // previously stored accounts containing fractional balances.
+ reserved 1;
Params params = 2 [(gogoproto.nullable) = false];
}
```
-## Account
-
-An `Account` is a struct representing the excess `akava` balance of an address.
-
-Since an address's total `akava` balance is derived from its `ukava` balance and the excess `akava` balance stored by the `Account` struct, the `akava` balance here should never exceed 1 `ukava` (10^12 `akava`).
-
-```protobuf
-message Account {
- bytes address = 1;
- string balance = 2;
-}
-```
-
## Deployed Cosmos Coin Contract Addresses
Addresses for the ERC20 contracts representing cosmos-sdk `Coin`s are kept in the module store. They are stored as bytes by the cosmos-sdk denom they represent.
diff --git a/x/evmutil/spec/README.md b/x/evmutil/spec/README.md
index 0b23cf51..7330d293 100644
--- a/x/evmutil/spec/README.md
+++ b/x/evmutil/spec/README.md
@@ -21,13 +21,6 @@ parent:
The evmutil module provides additional functionalities on top of the evm module.
-### EVM `akava` Usage
-
-evmutil stores additional state data for evm accounts and exposes an `EvmBankKeeper` that should be used by the `x/evm` keeper for bank operations.
-The purpose of the `EvmBankKeeper` is to allow the usage of the `akava` balance on the EVM via an account's existing `ukava` balance. This is needed because the EVM gas token use 18 decimals, and since `ukava` has 6 decimals, it cannot be used as the EVM gas denom directly.
-
-For additional details on how balance conversions work, see **[Concepts](01_concepts.md)**.
-
### ERC20 Token <> sdk.Coin Conversion
evmutil exposes messages to allow for the conversion of Kava ERC20 tokens and sdk.Coins via a whitelist.
diff --git a/x/evmutil/testutil/suite.go b/x/evmutil/testutil/suite.go
index c51453a8..a5407fe5 100644
--- a/x/evmutil/testutil/suite.go
+++ b/x/evmutil/testutil/suite.go
@@ -51,7 +51,6 @@ type Suite struct {
BankKeeper bankkeeper.Keeper
AccountKeeper authkeeper.AccountKeeper
Keeper keeper.Keeper
- EvmBankKeeper keeper.EvmBankKeeper
Addrs []sdk.AccAddress
EvmModuleAddr sdk.AccAddress
QueryClient types.QueryClient
@@ -69,7 +68,6 @@ func (suite *Suite) SetupTest() {
suite.BankKeeper = tApp.GetBankKeeper()
suite.AccountKeeper = tApp.GetAccountKeeper()
suite.Keeper = tApp.GetEvmutilKeeper()
- suite.EvmBankKeeper = keeper.NewEvmBankKeeper(tApp.GetEvmutilKeeper(), suite.BankKeeper, suite.AccountKeeper)
suite.EvmModuleAddr = suite.AccountKeeper.GetModuleAddress(evmtypes.ModuleName)
// test evm user keys that have no minting permissions
diff --git a/x/evmutil/types/expected_keepers.go b/x/evmutil/types/expected_keepers.go
index 31b4e4aa..1992f5d6 100644
--- a/x/evmutil/types/expected_keepers.go
+++ b/x/evmutil/types/expected_keepers.go
@@ -19,7 +19,11 @@ type AccountKeeper interface {
// BankKeeper defines the expected bank keeper interface
type BankKeeper interface {
- evmtypes.BankKeeper
+ GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
+ SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
+ SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
+ MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
+ BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
GetSupply(ctx sdk.Context, denom string) sdk.Coin
SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
diff --git a/x/evmutil/types/msg.go b/x/evmutil/types/msg.go
index 441c4f81..eec759de 100644
--- a/x/evmutil/types/msg.go
+++ b/x/evmutil/types/msg.go
@@ -214,7 +214,7 @@ func (MsgConvertCosmosCoinToERC20) Route() string { return RouterKey }
// Type implements legacytx.LegacyMsg
func (MsgConvertCosmosCoinToERC20) Type() string { return TypeMsgConvertCosmosCoinToERC20 }
-// NewMsgConvertCosmosCoinToERC20 returns a new MsgConvertCosmosCoinToERC20
+// NewMsgConvertCosmosCoinFromERC20 returns a new MsgConvertCosmosCoinToERC20
func NewMsgConvertCosmosCoinFromERC20(
initiator string,
receiver string,
diff --git a/x/precisebank/README.md b/x/precisebank/README.md
new file mode 100644
index 00000000..4f83d9d4
--- /dev/null
+++ b/x/precisebank/README.md
@@ -0,0 +1,650 @@
+# `x/precisebank`
+
+## Abstract
+
+This document specifies the precisebank module of Kava.
+
+The precisebank module is responsible for extending the precision of `x/bank`, intended to be used for the `x/evm`. It serves as a wrapper of `x/bank` to increase the precision of KAVA from 6 to 18 decimals, while preserving the behavior of existing `x/bank` balances.
+
+This module is used only by `x/evm` where 18 decimal points are expected.
+
+## Contents
+
+- [Background](#background)
+ - [Adding](#adding)
+ - [Subtracting](#subtracting)
+ - [Transfer](#transfer)
+ - [Setup](#setup)
+ - [Remainder does not change](#remainder-does-not-change)
+ - [Reserve](#reserve)
+ - [Burn](#burn)
+ - [Mint](#mint)
+- [State](#state)
+- [Keepers](#keepers)
+- [Messages](#messages)
+- [Events](#events)
+ - [Keeper Events](#keeper-events)
+ - [SendCoins](#sendcoins)
+ - [MintCoins](#mintcoins)
+ - [BurnCoins](#burncoins)
+- [Client](#client)
+ - [gRPC](#grpc)
+ - [TotalFractionalBalances](#totalfractionalbalances)
+ - [Remainder](#remainder)
+ - [FractionalBalance](#fractionalbalance)
+
+## Background
+
+The standard unit of currency on the Kava Chain is `KAVA`. This is denominated by the atomic unit `ukava`, which represents $10^{-6}$ `KAVA` and there are $10^6$ `ukava` per `KAVA`.
+
+In order to support 18 decimals of precision while maintaining `ukava` as the cosmos-native atomic unit, we further split each `ukava` unit into $10^{12}$ `akava` units, the native currency of the Kava EVM.
+
+This gives a full $10^{18}$ precision on the EVM. In order to avoid confusion with atomic `ukava` units, we will refer to `akava` as "sub-atomic units".
+
+To review we have:
+ - `ukava`, the cosmos-native unit and atomic unit of the Kava chain
+ - `akava`, the evm-native unit and sub-atomic unit of the Kava chain
+
+In order to maintain consistency between the `akava` supply and the `ukava` supply, we add the constraint that each sub-atomic `akava`, may only exist as part of an atomic `ukava`. Every `akava` is fully backed by a `ukava` in the `x/bank` module.
+
+This is a requirement since `ukava` balances in `x/bank` are shared between the cosmos modules and the EVM. We are wrapping and extending the `x/bank` module with the `x/precisebank` module to add an extra $10^{12}$ units of precision. If $10^{12}$ `akava` is transferred in the EVM, the cosmos modules will see a 1 `ukava` transfer and vice versa. If `akava` was not fully backed by `ukava`, then balance changes would not be fully consistent across the cosmos and the EVM.
+
+This brings us to how account balances are extended to represent `akava` balances larger than $10^{12}$. First, we define $a(n)$, $b(n)$, and $C$ where $a(n)$ is the `akava` balance of account `n`, $b(n)$ is the `ukava` balance of account `n` stored in the `x/bank` module, and $C$ is the conversion factor equal to $10^{12}$.
+
+Any $a(n)$ divisible by $C$, can be represented by $C$ * $b(n)$. Any remainder not divisible by $C$, we define the "fractional balance" as $f(n)$ and store this in the `x/precisebank` store.
+
+Thus,
+
+$$a(n) = b(n) \cdot C + f(n)$$
+
+where
+
+$$0 \le f(n) < C$$
+
+$$a(n), b(n) \ge 0$$
+
+This is the quotient-remainder theorem and any $a(n)$ can be represented by unique integers $b(n)$, $f(n)$ where
+
+$$b(n) = \lfloor a(n)/C \rfloor$$
+
+$$f(n) = a(n)\bmod{C}$$
+
+With this definition in mind we will refer to $b(n)$ units as integer units, and $f(n)$ as fractional units.
+
+Now since $f(n)$ is stored in the `x/precisebank` and not tracked by the `x/bank` keeper, these are not counted in the `ukava` supply, so if we define
+
+$$T_a \equiv \sum_{n \in \mathcal{A}}{a(n)}$$
+
+$$T_b \equiv \sum_{n \in \mathcal{A}}{b(n)}$$
+
+where $\mathcal{A}$ is the set of all accounts, $T_a$ is the total `akava` supply, and $T_b$ is the total `ukava` supply, then a reserve account $R$ is added such that
+
+$$a(R) = 0$$
+
+$$b(R) \cdot C = \sum_{n \in \mathcal{A}}{f(n)} + r$$
+
+where $R$ is the module account of the `x/precisebank`, and $r$ is the remainder or fractional amount backed by $b(R)$, but not yet in circulation such that
+
+$$T_a = T_b \cdot C - r$$
+
+and
+
+$$ 0 <= r < C$$
+
+We see that $0 \le T_b \cdot C - T_a < C$. If we mint, burn, or transfer `akava` such that this inequality would be invalid after updates to account balances, we adjust the $T_b$ supply by minting or burning to the reserve account which holds `ukava` equal to that of all `akava` balances less than `C` plus the remainder.
+
+If we didn't add these constraints, then the total supply of `ukava` reported by the bank keeper would not account for the `akava` units. We would incorrectly increase the supply of `akava` without increasing the reported total supply of KAVA.
+
+### Adding
+
+When adding we have
+
+$$a'(n) = a(n) + a$$
+
+$$b'(n) \cdot C + f'(n) = b(n) \cdot C + f(n) + a$$
+
+where $a'(n)$ is the new `akava` balance after adding `akava` amount $a$. These
+must hold true for all $a$. We can determine the new $b'(n)$ and $f'(n)$ with the following formula.
+
+$$f'(n) = f(n) + a \mod{C}$$
+
+$$b'(n) = \begin{cases} b(n) + \lfloor a/C \rfloor & f'(n) \geq f(n) \\
+b(n) + \lfloor a/C \rfloor + 1 & f'(n) < f(n) \end{cases}$$
+
+We can see that $b'(n)$ is incremented by an additional 1 integer unit if
+$f'(n) < f(n)$ because the new balance requires an arithmetic carry from the
+fractional to the integer unit.
+
+### Subtracting
+
+When subtracting we have
+
+$$a'(n) = a(n) - a$$
+
+$$b'(n) \cdot C + f'(n) = b(n) \cdot C + f(n) - a$$
+
+and
+
+$$f'(n) = f(n) - a \mod{C}$$
+
+$$b'(n) = \begin{cases} b(n) - \lfloor a/C \rfloor & f'(n) \leq f(n) \\
+b(n) - \lfloor a/C \rfloor - 1 & f'(n) > f(n) \end{cases}$$
+
+Similar to the adding case, we subtract $b'(n)$ by an additional 1 if
+$f'(n) > f(n)$ because $f(n)$ is insufficient on its own and requires an
+arithmetic borrow from the integer units.
+
+### Transfer
+
+A transfer is a combination of adding and subtracting of a single amount between
+two different accounts. The transfer is valid if both the subtraction for the
+sender and the addition for the receiver are valid.
+
+#### Setup
+
+Let two accounts $1$ and $2$ have balances $a(1)$ and $a(2)$, and $a$ is the
+amount to transfer. Assume that $a(1) \ge a$ to ensure that the transfer is
+valid. We initiate a transfer by subtracting $a$ from account $1$ and adding $a$
+to account $2$, yielding
+
+$$a'(1) = a(1) - a$$
+
+$$a'(2) = a(2) + a$$
+
+The reserve account must also be updated to reflect the change in the total
+supply of fractional units.
+
+$$b(R) \cdot C = \sum_{n \in \mathcal{A}}{f(n)} + r$$
+
+$$b'(R) \cdot C = \sum_{n \in \mathcal{A}}{f'(n)} + r'$$
+
+With these two formulas, we can determine the new remainder and reserve by using
+the delta of the sum of fractional units and the remainder.
+
+$$(b'(R)-b(R)) \cdot C = \sum_{n \in \mathcal{A}}{f'(n)} - \sum_{n \in \mathcal{A}}{f(n)} + r' - r$$
+
+Since only two accounts are involved in the transfer, we can use the two account
+balances in place of the fractional sum delta.
+
+$$(b'(R)-b(R)) \cdot C = f'(1) - f(1) + f'(2) - f(2) + r' - r$$
+
+#### Remainder does not change
+
+Take $\mod{C}$ of both sides of the equation.
+
+$$(b'(R)-b(R)) \cdot C \mod{C} = [f'(1) - f(1) + f'(2) - f(2) + r' - r] \mod{C}$$
+
+Since $C$ is a multiple of $C$, the left side of the equation is $0$.
+
+$$0 = f'(1) - f(1) + f'(2) - f(2) + r' - r \mod{C}$$
+
+Replace $f'(1)$ and $f'(2)$ with their definitions in terms of $f(1)$ and $f(2)$.
+
+$$0 = (f(1) - a)\bmod{C} - f(1) + (f(2) + a)\bmod{C} - f(2) + r' - r \mod{C}$$
+
+This can be simplified to:
+
+$$0 = f(1) - a - f(1) + f(2) + a - f(2) + r' - r \mod{C}$$
+
+Canceling out terms $a$, $f(1)$ and $f(2)$.
+
+$$0 = r' - r \mod{C}$$
+
+By the quotient remainder theorem, we can express $r' - r$ as:
+
+$$q * C = r' - r$$
+
+for some integer $q$.
+
+With our known range of $r$ and $r'$:
+
+$$0 \leq r' < C, 0 \leq r < C$$
+
+We can see that $r' - r$ must be in the range
+
+$$ -C < r' - r < C$$
+
+This implies that $q$ must be $0$ as there is no other integer $q$ that satisfies the inequality.
+
+$$ -C < q * C < C$$
+
+$$q = 0$$
+
+$$ r' - r = 0$$
+
+Therefore, the remainder does not change during a transfer.
+
+#### Reserve
+
+The reserve account must be updated to reflect the change in the total supply of fractional units.
+
+The change in reserve is determined by the change in the fractional units of the two accounts.
+
+$$(b'(R)-b(R)) \cdot C = f'(1) - f(1) + f'(2) - f(2)$$
+
+For $f'(1)$, we can represent the new fractional balance as:
+
+$$f'(1) = f(1) - a \mod{C}$$
+
+$$f'(1)\bmod{C}= f(1)\bmod{C} - a \bmod{C} \mod{C}$$
+
+$$f'(1) = f(1) - a \bmod{C} \mod{C}$$
+
+This results in two cases for $f'(1)$:
+
+$$f'(1) = \begin{cases} f(1) - a\bmod{C} & 0 \leq f(1) - a\bmod{C} \\
+f(1) - a\bmod{C} + C & 0 > f(1) - a\bmod{C} \end{cases}$$
+
+Since we can identify the following:
+
+$$f'(1) \leq f(1) \Longleftrightarrow f'(1) = f(1) - a\bmod{C} $$
+
+$$f'(1) > f(1) \Longleftrightarrow f'(1) = f(1) - a\bmod{C} + C$$
+
+We can simplify the two cases for $f'(1)$:
+
+$$f'(1) = \begin{cases} f(1) - a\bmod{C} & f'(1) \leq f(1) \\
+f(1) - a\bmod{C} + C & f'(1) > f(1) \end{cases}$$
+
+The same for $f'(2)$:
+
+$$f'(2) = f(2) + a \mod{C}$$
+
+$$f'(2)\bmod{C}= f(2)\bmod{C} + a \bmod{C} \mod{C}$$
+
+$$f'(2) = f(2) + a \bmod{C} \mod{C}$$
+
+$$f'(2) = \begin{cases} f(2) + a\bmod{C} & f'(2) \geq f(2) \\
+f(2) + a\bmod{C} - C & f'(2) < f(2) \end{cases}$$
+
+Bringing the two cases for the two accounts together to determine the change in the reserve account:
+
+$$b'(R) - b(R) \cdot C = \begin{cases} f(1) - a\bmod{C} + C - f(1) + f(2) + a\bmod{C} - C + f(2) & f'(1) > f(1) \land f'(2) < f(2) \\
+f(1) - a\bmod{C} - f(1) + f(2) + a\bmod{C} - C + f(2) & f'(1) \leq f(1) \land f'(2) < f(2) \\
+f(1) - a\bmod{C} + C - f(1) + f(2) + a\bmod{C} + f(2) & f'(1) > f(1) \land f'(2) \geq f(2) \\
+f(1) - a\bmod{C} - f(1) + f(2) + a\bmod{C} + f(2) & f'(1) \leq f(1) \land f'(2) \geq f(2) \\
+\end{cases}$$
+
+This simplifies to:
+
+$$b'(R) - b(R) \cdot C = \begin{cases} 0 & f'(1) > f(1) \land f'(2) < f(2) \\
+-C & f'(1) \leq f(1) \land f'(2) < f(2) \\
+C & f'(1) > f(1) \land f'(2) \geq f(2) \\
+0 & f'(1) \leq f(1) \land f'(2) \geq f(2) \\
+\end{cases}$$
+
+Simplifying further by dividing by $C$:
+
+$$b'(R) - b(R) = \begin{cases} 0 & f'(1) > f(1) \land f'(2) < f(2) \\
+-1 & f'(1) \leq f(1) \land f'(2) < f(2) \\
+1 & f'(1) > f(1) \land f'(2) \geq f(2) \\
+0 & f'(1) \leq f(1) \land f'(2) \geq f(2) \\
+\end{cases}$$
+
+Thus the reserve account is updated based on the changes in the fractional units of the two accounts.
+
+### Burn
+
+When burning, we change only 1 account. Assume we are burning an amount $a$ from account $1$.
+
+$$a'(1) = a(1) - a$$
+
+The change in reserve is determined by the change in the fractional units of the account and the remainder.
+
+$$(b'(R)-b(R)) \cdot C = f'(1) - f(1) + r' - r$$
+
+The new fractional balance is:
+
+$$f'(1) = f(1) - a \mod{C}$$
+
+Apply modulo $C$ to both sides of the equation.
+
+$$f'(1)\bmod{C}= f(1)\bmod{C} - a \bmod{C} \mod{C}$$
+
+This simplifies to:
+
+$$f'(1) = f(1) - a \bmod{C} \mod{C}$$
+
+We can see two cases for $f'(1)$, depending on whether the new fractional balance is less than the old fractional balance.
+
+$$f'(1) = \begin{cases} f(1) - a\bmod{C} & f'(1) \leq f(1) \\
+f(1) - a\bmod{C} + C & f'(1) > f(1) \end{cases}$$
+
+The second case occurs when we need to borrow from the integer units.
+
+We update the remainder by adding $a$ to $r$ as burning increases the amount no longer in circulation but still backed by the reserve.
+
+$$r' = r + a \mod{C}$$
+
+$$r'\bmod{C}= r\bmod{C} + a \bmod{C} \mod{C}$$
+
+$$r' = r + a \bmod{C} \mod{C}$$
+
+We can see two cases for $r'$, depending on whether the new remainder is less than the old remainder.
+
+$$r' = \begin{cases} r + a\bmod{C} & r' \geq r \\
+r + a\bmod{C} - C & r' < r \end{cases}$$
+
+The reserve account is updated based on the changes in the fractional units of the account and remainder.
+
+$$b'(R) - b(R) = \begin{cases} 0 & f'(1) > f(1) \land r' < r \\
+-1 & f'(1) \leq f(1) \land r' < r \\
+1 & f'(1) > f(1) \land r' \geq r \\
+0 & f'(1) \leq f(1) \land r' \geq r \\
+\end{cases}$$
+
+### Mint
+
+Minting is similar to burning, but we add to the account instead of
+removing it. Assume we are minting an amount $a$ to account $1$.
+
+$$a'(1) = a(1) + a$$
+
+The change in reserve is determined by the change in the fractional units of the account and the remainder.
+
+$$(b'(R)-b(R)) \cdot C = f'(1) - f(1) + r' - r$$
+
+The new fractional balance is:
+
+$$f'(1) = f(1) + a \mod{C}$$
+
+Apply modulo $C$ to both sides of the equation.
+
+$$f'(1)\bmod{C}= f(1)\bmod{C} + a \bmod{C} \mod{C}$$
+
+$$f'(1) = f(1) + a \bmod{C} \mod{C}$$
+
+We can see two cases for $f'(1)$, depending on whether the new fractional balance is greater than the old fractional balance.
+
+$$f'(1) = \begin{cases} f(1) + a\bmod{C} & f'(1) \geq f(1) \\
+f(1) + a\bmod{C} - C & f'(1) < f(1) \end{cases}$$
+
+The second case occurs when we need to carry to the integer unit.
+
+We update the remainder by subtracting $a$ from $r$ as minting decreases the amount no longer in circulation but still backed by the reserve.
+
+$$r' = r - a \mod{C}$$
+
+$$r'\bmod{C}= r\bmod{C} - a \bmod{C} \mod{C}$$
+
+$$r' = r - a \bmod{C} \mod{C}$$
+
+$$r' = \begin{cases} r - a\bmod{C} & r' \leq r \\
+r - a\bmod{C} + C & r' > r \end{cases}$$
+
+The reserve account is updated based on the changes in the fractional units of the account and the remainder.
+
+$$b'(R) - b(R) = \begin{cases} 0 & r' > r \land f'(1) < f(1) \\
+-1 & r' \leq r \land f'(1) < f(1) \\
+1 & r' > r \land f'(1) \geq f(1) \\
+0 & r' \leq r \land f'(1) \geq f(1) \\
+\end{cases}$$
+
+## State
+
+The `x/precisebank` module keeps state of the following:
+1. Account fractional balances.
+2. Remainder amount. This amount represents the fractional amount that is backed
+ by the reserve account but not yet in circulation. This can be non-zero if
+ a fractional amount less than `1ukava` is minted.
+
+ **Note:** Currently, mint and burns are only used to transfer fractional
+ amounts between accounts via `x/evm`. This means mint and burns on mainnet
+ state will always be equal and opposite, always resulting in a zero remainder
+ at the end of each transaction and block.
+
+The `x/precisebank` module does not keep track of the reserve as it is stored in
+the `x/bank` module.
+
+## Keepers
+
+The `x/precisebank module only exposes one keeper that wraps the bank module`
+keeper and implements bank keeper compatible methods to support extended coin.
+This complies with the `x/evm` module interface for `BankKeeper`.
+
+```go
+type BankKeeper interface {
+ authtypes.BankKeeper
+ SpendableCoin(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
+ SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
+ MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
+ BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
+}
+```
+
+## Messages
+
+The `x/precisebank` module does not have any messages and is intended to be used
+by other modules as a replacement of the bank module.
+
+## Events
+
+### Keeper Events
+
+The `x/precisebank` module emits the following events, that are meant to be
+match the events emitted by the `x/bank` module. Events emitted by
+`x/precisebank` will only contain `akava` amounts, as the `x/bank` module will
+emit events with all other denoms. This means if an account transfers multiple
+coins including `akava`, the `x/precisebank` module will emit an event with the
+full `akava` amount. If `ukava` is included in a transfer, mint, or burn, the
+`x/precisebank` module will emit an event with the full equivalent `akava`
+amount.
+
+#### SendCoins
+
+```json
+{
+ "type": "transfer",
+ "attributes": [
+ {
+ "key": "recipient",
+ "value": "{{sdk.AccAddress of the recipient}}",
+ "index": true
+ },
+ {
+ "key": "sender",
+ "value": "{{sdk.AccAddress of the sender}}",
+ "index": true
+ },
+ {
+ "key": "amount",
+ "value": "{{sdk.Coins being transferred}}",
+ "index": true
+ }
+ ]
+}
+```
+
+```json
+{
+ "type": "coin_spent",
+ "attributes": [
+ {
+ "key": "spender",
+ "value": "{{sdk.AccAddress of the address which is spending coins}}",
+ "index": true
+ },
+ {
+ "key": "amount",
+ "value": "{{sdk.Coins being spent}}",
+ "index": true
+ }
+ ]
+}
+```
+
+```json
+{
+ "type": "coin_received",
+ "attributes": [
+ {
+ "key": "receiver",
+ "value": "{{sdk.AccAddress of the address beneficiary of the coins}}",
+ "index": true
+ },
+ {
+ "key": "amount",
+ "value": "{{sdk.Coins being received}}",
+ "index": true
+ }
+ ]
+}
+```
+
+#### MintCoins
+
+```json
+{
+ "type": "coinbase",
+ "attributes": [
+ {
+ "key": "minter",
+ "value": "{{sdk.AccAddress of the module minting coins}}",
+ "index": true
+ },
+ {
+ "key": "amount",
+ "value": "{{sdk.Coins being minted}}",
+ "index": true
+ }
+ ]
+}
+```
+
+```json
+{
+ "type": "coin_received",
+ "attributes": [
+ {
+ "key": "receiver",
+ "value": "{{sdk.AccAddress of the module minting coins}}",
+ "index": true
+ },
+ {
+ "key": "amount",
+ "value": "{{sdk.Coins being received}}",
+ "index": true
+ }
+ ]
+}
+```
+
+#### BurnCoins
+
+```json
+{
+ "type": "burn",
+ "attributes": [
+ {
+ "key": "burner",
+ "value": "{{sdk.AccAddress of the module burning coins}}",
+ "index": true
+ },
+ {
+ "key": "amount",
+ "value": "{{sdk.Coins being burned}}",
+ "index": true
+ }
+ ]
+}
+```
+
+```json
+{
+ "type": "coin_spent",
+ "attributes": [
+ {
+ "key": "spender",
+ "value": "{{sdk.AccAddress of the module burning coins}}",
+ "index": true
+ },
+ {
+ "key": "amount",
+ "value": "{{sdk.Coins being burned}}",
+ "index": true
+ }
+ ]
+}
+```
+
+## Client
+
+### gRPC
+
+A user can query the precisebank module using gRPC endpoints.
+
+#### TotalFractionalBalances
+
+The `TotalFractionalBalances` endpoint allows users to query the aggregate sum
+of all fractional balances. This is primarily used for external verification of
+the module state against the reserve balance.
+
+```shell
+kava.precisebank.v1.Query/TotalFractionalBalances
+```
+
+Example:
+
+```shell
+grpcurl -plaintext \
+ localhost:9090 \
+ kava.precisebank.v1.Query/TotalFractionalBalances
+```
+
+Example Output:
+
+```json
+{
+ "total": "2000000000000akava"
+}
+```
+
+#### Remainder
+
+The `Remainder` endpoint allows users to query the current remainder amount.
+
+```shell
+kava.precisebank.v1.Query/Remainder
+```
+
+Example:
+
+```shell
+grpcurl -plaintext \
+ localhost:9090 \
+ kava.precisebank.v1.Query/Remainder
+```
+
+Example Output:
+
+```json
+{
+ "remainder": "100akava"
+}
+```
+
+#### FractionalBalance
+
+The `FractionalBalance` endpoint allows users to query the fractional balance of
+a specific account.
+
+```shell
+kava.precisebank.v1.Query/FractionalBalance
+```
+
+Example:
+
+```shell
+grpcurl -plaintext \
+ -d '{"address": "kava1..."}' \
+ localhost:9090 \
+ kava.precisebank.v1.Query/FractionalBalance
+```
+
+Example Output:
+
+```json
+{
+ "fractional_balance": "10000akava"
+}
+```
diff --git a/x/precisebank/genesis.go b/x/precisebank/genesis.go
new file mode 100644
index 00000000..86d3c97f
--- /dev/null
+++ b/x/precisebank/genesis.go
@@ -0,0 +1,73 @@
+package precisebank
+
+import (
+ "fmt"
+
+ sdkmath "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+// InitGenesis initializes the store state from a genesis state.
+func InitGenesis(
+ ctx sdk.Context,
+ keeper keeper.Keeper,
+ ak types.AccountKeeper,
+ bk types.BankKeeper,
+ gs *types.GenesisState,
+) {
+ // Ensure the genesis state is valid
+ if err := gs.Validate(); err != nil {
+ panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err))
+ }
+
+ // Initialize module account
+ if moduleAcc := ak.GetModuleAccount(ctx, types.ModuleName); moduleAcc == nil {
+ panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
+ }
+
+ // Check module balance matches sum of fractional balances + remainder
+ // This is always a whole integer amount, as previously verified in
+ // GenesisState.Validate()
+ totalAmt := gs.TotalAmountWithRemainder()
+
+ moduleAddr := ak.GetModuleAddress(types.ModuleName)
+ moduleBal := bk.GetBalance(ctx, moduleAddr, types.IntegerCoinDenom)
+ moduleBalExtended := moduleBal.Amount.Mul(types.ConversionFactor())
+
+ // Compare balances in full precise extended amounts
+ if !totalAmt.Equal(moduleBalExtended) {
+ panic(fmt.Sprintf(
+ "module account balance does not match sum of fractional balances and remainder, balance is %s but expected %v%s (%v%s)",
+ moduleBal,
+ totalAmt, types.ExtendedCoinDenom,
+ totalAmt.Quo(types.ConversionFactor()), types.IntegerCoinDenom,
+ ))
+ }
+
+ // Set FractionalBalances in state
+ for _, bal := range gs.Balances {
+ addr := sdk.MustAccAddressFromBech32(bal.Address)
+
+ keeper.SetFractionalBalance(ctx, addr, bal.Amount)
+ }
+
+ // Set remainder amount in state
+ keeper.SetRemainderAmount(ctx, gs.Remainder)
+}
+
+// ExportGenesis returns a GenesisState for a given context and keeper.
+func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState {
+ balances := types.FractionalBalances{}
+ keeper.IterateFractionalBalances(ctx, func(addr sdk.AccAddress, amount sdkmath.Int) bool {
+ balances = append(balances, types.NewFractionalBalance(addr.String(), amount))
+
+ return false
+ })
+
+ remainder := keeper.GetRemainderAmount(ctx)
+
+ return types.NewGenesisState(balances, remainder)
+}
diff --git a/x/precisebank/genesis_test.go b/x/precisebank/genesis_test.go
new file mode 100644
index 00000000..1e8a318a
--- /dev/null
+++ b/x/precisebank/genesis_test.go
@@ -0,0 +1,271 @@
+package precisebank_test
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank"
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/testutil"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ "github.com/stretchr/testify/suite"
+)
+
+type GenesisTestSuite struct {
+ testutil.Suite
+}
+
+func TestGenesisTestSuite(t *testing.T) {
+ suite.Run(t, new(GenesisTestSuite))
+}
+
+func (suite *GenesisTestSuite) TestInitGenesis() {
+ tests := []struct {
+ name string
+ setupFn func()
+ genesisState *types.GenesisState
+ panicMsg string
+ }{
+ {
+ "valid - default genesisState",
+ func() {},
+ types.DefaultGenesisState(),
+ "",
+ },
+ {
+ "valid - empty genesisState",
+ func() {},
+ &types.GenesisState{},
+ "failed to validate precisebank genesis state: nil remainder amount",
+ },
+ {
+ "valid - module balance matches non-zero amount",
+ func() {
+ // Set module account balance to expected amount
+ err := suite.BankKeeper.MintCoins(
+ suite.Ctx,
+ types.ModuleName,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdkmath.NewInt(2))),
+ )
+ suite.Require().NoError(err)
+ },
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().SubRaw(1)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().SubRaw(1)),
+ },
+ // 2 leftover from 0.999... + 0.999...
+ sdkmath.NewInt(2),
+ ),
+ "",
+ },
+ {
+ // Other GenesisState.Validate() tests are in types/genesis_test.go
+ "invalid genesisState - GenesisState.Validate() is called",
+ func() {},
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ },
+ sdkmath.ZeroInt(),
+ ),
+ "failed to validate precisebank genesis state: invalid balances: duplicate address 0g1qy20a4yf",
+ },
+ {
+ "invalid - module balance insufficient",
+ func() {},
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().SubRaw(1)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().SubRaw(1)),
+ },
+ // 2 leftover from 0.999... + 0.999...
+ sdkmath.NewInt(2),
+ ),
+ "module account balance does not match sum of fractional balances and remainder, balance is 0ua0gi but expected 2000000000000neuron (2ua0gi)",
+ },
+ {
+ "invalid - module balance excessive",
+ func() {
+ // Set module account balance to greater than expected amount
+ err := suite.BankKeeper.MintCoins(
+ suite.Ctx,
+ types.ModuleName,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdkmath.NewInt(100))),
+ )
+ suite.Require().NoError(err)
+ },
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().SubRaw(1)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().SubRaw(1)),
+ },
+ sdkmath.NewInt(2),
+ ),
+ "module account balance does not match sum of fractional balances and remainder, balance is 100ua0gi but expected 2000000000000neuron (2ua0gi)",
+ },
+ {
+ "sets module account",
+ func() {
+ // Delete the module account first to ensure it's created here
+ moduleAcc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
+ suite.AccountKeeper.RemoveAccount(suite.Ctx, moduleAcc)
+
+ // Ensure module account is deleted in state.
+ // GetModuleAccount() will always return non-nil and does not
+ // necessarily equate to the account being stored in the account store.
+ suite.Require().Nil(suite.AccountKeeper.GetAccount(suite.Ctx, moduleAcc.GetAddress()))
+ },
+ types.DefaultGenesisState(),
+ "",
+ },
+ }
+
+ for _, tc := range tests {
+ suite.Run(tc.name, func() {
+ suite.SetupTest()
+ tc.setupFn()
+
+ if tc.panicMsg != "" {
+ suite.Require().PanicsWithValue(
+ tc.panicMsg,
+ func() {
+ precisebank.InitGenesis(
+ suite.Ctx,
+ suite.Keeper,
+ suite.AccountKeeper,
+ suite.BankKeeper,
+ tc.genesisState,
+ )
+ },
+ )
+
+ return
+ }
+
+ suite.Require().NotPanics(func() {
+ precisebank.InitGenesis(
+ suite.Ctx,
+ suite.Keeper,
+ suite.AccountKeeper,
+ suite.BankKeeper,
+ tc.genesisState,
+ )
+ })
+
+ // Ensure module account is created
+ moduleAcc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
+ suite.NotNil(moduleAcc)
+ suite.NotNil(
+ suite.AccountKeeper.GetAccount(suite.Ctx, moduleAcc.GetAddress()),
+ "module account should be created & stored in account store",
+ )
+
+ // Verify balances are set in state, get full list of balances in
+ // state to ensure they are set AND no extra balances are set
+ var bals []types.FractionalBalance
+ suite.Keeper.IterateFractionalBalances(suite.Ctx, func(addr sdk.AccAddress, bal sdkmath.Int) bool {
+ bals = append(bals, types.NewFractionalBalance(addr.String(), bal))
+
+ return false
+ })
+
+ suite.Require().ElementsMatch(tc.genesisState.Balances, bals, "balances should be set in state")
+
+ remainder := suite.Keeper.GetRemainderAmount(suite.Ctx)
+ suite.Require().Equal(tc.genesisState.Remainder, remainder, "remainder should be set in state")
+
+ // Additional verification of state via invariants
+ invariantFn := keeper.AllInvariants(suite.Keeper)
+ msg, broken := invariantFn(suite.Ctx)
+ suite.Require().False(broken, "invariants should not be broken after InitGenesis")
+ suite.Require().Empty(msg, "invariants should not return a message after InitGenesis")
+ })
+ }
+}
+
+func (suite *GenesisTestSuite) TestExportGenesis() {
+ // ExportGenesis(InitGenesis(genesisState)) == genesisState
+ // Must also be valid.
+
+ tests := []struct {
+ name string
+ initGenesisState func() *types.GenesisState
+ }{
+ {
+ "InitGenesis(DefaultGenesisState)",
+ func() *types.GenesisState {
+ return types.DefaultGenesisState()
+ },
+ },
+ {
+ "balances, no remainder",
+ func() *types.GenesisState {
+ err := suite.BankKeeper.MintCoins(
+ suite.Ctx,
+ types.ModuleName,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdkmath.NewInt(1))),
+ )
+ suite.Require().NoError(err)
+
+ return types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().QuoRaw(2)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().QuoRaw(2)),
+ },
+ sdkmath.ZeroInt(),
+ )
+ },
+ },
+ {
+ "balances, remainder",
+ func() *types.GenesisState {
+ err := suite.BankKeeper.MintCoins(
+ suite.Ctx,
+ types.ModuleName,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdkmath.NewInt(1))),
+ )
+ suite.Require().NoError(err)
+
+ return types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().QuoRaw(2)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().QuoRaw(2).SubRaw(1)),
+ },
+ sdkmath.OneInt(),
+ )
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ suite.Run(tc.name, func() {
+ // Reset state
+ suite.SetupTest()
+
+ initGs := tc.initGenesisState()
+
+ suite.Require().NotPanics(func() {
+ precisebank.InitGenesis(
+ suite.Ctx,
+ suite.Keeper,
+ suite.AccountKeeper,
+ suite.BankKeeper,
+ initGs,
+ )
+ })
+
+ genesisState := precisebank.ExportGenesis(suite.Ctx, suite.Keeper)
+ suite.Require().NoError(genesisState.Validate(), "exported genesis state should be valid")
+
+ suite.Require().Equal(
+ initGs,
+ genesisState,
+ "exported genesis state should equal initial genesis state",
+ )
+ })
+ }
+}
diff --git a/x/precisebank/keeper/burn.go b/x/precisebank/keeper/burn.go
new file mode 100644
index 00000000..1eebd394
--- /dev/null
+++ b/x/precisebank/keeper/burn.go
@@ -0,0 +1,182 @@
+package keeper
+
+import (
+ "fmt"
+
+ errorsmod "cosmossdk.io/errors"
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
+)
+
+// BurnCoins burns coins deletes coins from the balance of the module account.
+// It will panic if the module account does not exist or is unauthorized.
+func (k Keeper) BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error {
+ // Custom protection for x/precisebank, no external module should be able to
+ // affect reserves.
+ if moduleName == types.ModuleName {
+ panic(errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "module account %s cannot be burned from", moduleName))
+ }
+
+ // Panic errors are identical to x/bank for consistency.
+ acc := k.ak.GetModuleAccount(ctx, moduleName)
+ if acc == nil {
+ panic(errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName))
+ }
+
+ if !acc.HasPermission(authtypes.Burner) {
+ panic(errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to burn tokens", moduleName))
+ }
+
+ // Ensure the coins are valid before burning
+ if !amt.IsValid() {
+ return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
+ }
+
+ // Get non-ExtendedCoinDenom coins
+ passthroughCoins := amt
+
+ extendedAmount := amt.AmountOf(types.ExtendedCoinDenom)
+ if extendedAmount.IsPositive() {
+ // Remove ExtendedCoinDenom from the coins as it is managed by x/precisebank
+ removeCoin := sdk.NewCoin(types.ExtendedCoinDenom, extendedAmount)
+ passthroughCoins = amt.Sub(removeCoin)
+ }
+
+ // Coins unmanaged by x/precisebank are passed through to x/bank
+ if !passthroughCoins.Empty() {
+ if err := k.bk.BurnCoins(ctx, moduleName, passthroughCoins); err != nil {
+ return err
+ }
+ }
+
+ // Only burn extended coin if the amount is positive
+ if extendedAmount.IsPositive() {
+ if err := k.burnExtendedCoin(ctx, moduleName, extendedAmount); err != nil {
+ return err
+ }
+ }
+
+ fullEmissionCoins := sdk.NewCoins(types.SumExtendedCoin(amt))
+ if fullEmissionCoins.IsZero() {
+ return nil
+ }
+
+ ctx.EventManager().EmitEvents(sdk.Events{
+ banktypes.NewCoinBurnEvent(acc.GetAddress(), fullEmissionCoins),
+ banktypes.NewCoinSpentEvent(acc.GetAddress(), fullEmissionCoins),
+ })
+
+ return nil
+}
+
+// burnExtendedCoin burns the fractional amount of the ExtendedCoinDenom from the module account.
+func (k Keeper) burnExtendedCoin(
+ ctx sdk.Context,
+ moduleName string,
+ amt sdkmath.Int,
+) error {
+ // Get the module address
+ moduleAddr := k.ak.GetModuleAddress(moduleName)
+
+ // We only need the fractional balance to burn coins, as integer burns will
+ // return errors on insufficient funds.
+ prevFractionalBalance := k.GetFractionalBalance(ctx, moduleAddr)
+
+ // Get remainder amount first to optimize direct burn.
+ prevRemainder := k.GetRemainderAmount(ctx)
+
+ // -------------------------------------------------------------------------
+ // Pure stateless calculations
+
+ integerBurnAmount := amt.Quo(types.ConversionFactor())
+ fractionalBurnAmount := amt.Mod(types.ConversionFactor())
+
+ // newFractionalBalance can be negative if fractional balance is insufficient.
+ newFractionalBalance := prevFractionalBalance.Sub(fractionalBurnAmount)
+
+ // If true, fractional balance is insufficient and will require an integer
+ // borrow.
+ requiresBorrow := newFractionalBalance.IsNegative()
+
+ // Add to new remainder with burned fractional amount.
+ newRemainder := prevRemainder.Add(fractionalBurnAmount)
+
+ // If true, remainder has accumulated enough fractional amounts to burn 1
+ // integer coin.
+ overflowingRemainder := newRemainder.GTE(types.ConversionFactor())
+
+ // -------------------------------------------------------------------------
+ // Stateful operations for burn
+
+ // Not enough fractional balance:
+ // 1. If the new remainder incurs an additional reserve burn, we can just
+ // burn an additional integer coin from the account directly instead as
+ // an optimization.
+ // 2. If the new remainder is still under conversion factor (no extra
+ // reserve burn) then we need to transfer 1 integer coin to the reserve
+ // for the integer borrow.
+
+ // Case #1: (optimization) direct burn instead of borrow (reserve transfer)
+ // & reserve burn. No additional reserve burn would be necessary after this.
+ if requiresBorrow && overflowingRemainder {
+ newFractionalBalance = newFractionalBalance.Add(types.ConversionFactor())
+ newRemainder = newRemainder.Sub(types.ConversionFactor())
+
+ integerBurnAmount = integerBurnAmount.AddRaw(1)
+ }
+
+ // Case #2: Transfer 1 integer coin to reserve for integer borrow to ensure
+ // reserve fully backs the fractional amount.
+ if requiresBorrow && !overflowingRemainder {
+ newFractionalBalance = newFractionalBalance.Add(types.ConversionFactor())
+
+ // Transfer 1 integer coin to reserve to cover the borrowed fractional
+ // amount. SendCoinsFromModuleToModule will return an error if the
+ // module account has insufficient funds and an error with the full
+ // extended balance will be returned.
+ borrowCoin := sdk.NewCoin(types.IntegerCoinDenom, sdkmath.OneInt())
+ if err := k.bk.SendCoinsFromModuleToModule(
+ ctx,
+ moduleName,
+ types.ModuleName, // borrowed integer is transferred to reserve
+ sdk.NewCoins(borrowCoin),
+ ); err != nil {
+ return k.updateInsufficientFundsError(ctx, moduleAddr, amt, err)
+ }
+ }
+
+ // Case #3: Does not require borrow, but remainder has accumulated enough
+ // fractional amounts to burn 1 integer coin.
+ if !requiresBorrow && overflowingRemainder {
+ reserveBurnCoins := sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdkmath.OneInt()))
+ if err := k.bk.BurnCoins(ctx, types.ModuleName, reserveBurnCoins); err != nil {
+ return fmt.Errorf("failed to burn %s for reserve: %w", reserveBurnCoins, err)
+ }
+
+ newRemainder = newRemainder.Sub(types.ConversionFactor())
+ }
+
+ // Case #4: No additional work required, no borrow needed and no additional
+ // reserve burn
+
+ // Burn the integer amount - this may include the extra optimization burn
+ // from case #1
+ if !integerBurnAmount.IsZero() {
+ coin := sdk.NewCoin(types.IntegerCoinDenom, integerBurnAmount)
+ if err := k.bk.BurnCoins(ctx, moduleName, sdk.NewCoins(coin)); err != nil {
+ return k.updateInsufficientFundsError(ctx, moduleAddr, amt, err)
+ }
+ }
+
+ // Assign new fractional balance in x/precisebank
+ k.SetFractionalBalance(ctx, moduleAddr, newFractionalBalance)
+
+ // Update remainder for burned fractional coins
+ k.SetRemainderAmount(ctx, newRemainder)
+
+ return nil
+}
diff --git a/x/precisebank/keeper/burn_integration_test.go b/x/precisebank/keeper/burn_integration_test.go
new file mode 100644
index 00000000..3e457b2a
--- /dev/null
+++ b/x/precisebank/keeper/burn_integration_test.go
@@ -0,0 +1,510 @@
+package keeper_test
+
+import (
+ "fmt"
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/testutil"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
+ ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
+ "github.com/stretchr/testify/suite"
+)
+
+type burnIntegrationTestSuite struct {
+ testutil.Suite
+}
+
+func (suite *burnIntegrationTestSuite) SetupTest() {
+ suite.Suite.SetupTest()
+}
+
+func TestBurnIntegrationTest(t *testing.T) {
+ suite.Run(t, new(burnIntegrationTestSuite))
+}
+
+func (suite *burnIntegrationTestSuite) TestBurnCoins_MatchingErrors() {
+ // x/precisebank BurnCoins should be identical to x/bank BurnCoins to
+ // consumers. This test ensures that the panics & errors returned by
+ // x/precisebank are identical to x/bank.
+
+ tests := []struct {
+ name string
+ recipientModule string
+ setupFn func()
+ burnAmount sdk.Coins
+ wantErr string
+ wantPanic string
+ }{
+ {
+ "invalid module",
+ "notamodule",
+ func() {},
+ cs(c("ukava", 1000)),
+ "",
+ "module account notamodule does not exist: unknown address",
+ },
+ {
+ "no burn permissions",
+ // Check app.go to ensure this module has no burn permissions
+ authtypes.FeeCollectorName,
+ func() {},
+ cs(c("ukava", 1000)),
+ "",
+ "module account fee_collector does not have permissions to burn tokens: unauthorized",
+ },
+ {
+ "invalid amount",
+ // Has burn permissions so it goes to the amt check
+ ibctransfertypes.ModuleName,
+ func() {},
+ sdk.Coins{sdk.Coin{Denom: "ukava", Amount: sdkmath.NewInt(-100)}},
+ "-100ukava: invalid coins",
+ "",
+ },
+ {
+ "insufficient balance - empty",
+ ibctransfertypes.ModuleName,
+ func() {},
+ cs(c("ukava", 1000)),
+ "spendable balance is smaller than 1000ukava: insufficient funds",
+ "",
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ // Reset
+ suite.SetupTest()
+
+ if tt.wantErr == "" && tt.wantPanic == "" {
+ suite.Fail("test must specify either wantErr or wantPanic")
+ }
+
+ if tt.wantErr != "" {
+ // Check x/bank BurnCoins for identical error
+ bankErr := suite.BankKeeper.BurnCoins(suite.Ctx, tt.recipientModule, tt.burnAmount)
+ suite.Require().Error(bankErr)
+ suite.Require().EqualError(bankErr, tt.wantErr, "expected error should match x/bank BurnCoins error")
+
+ pbankErr := suite.Keeper.BurnCoins(suite.Ctx, tt.recipientModule, tt.burnAmount)
+ suite.Require().Error(pbankErr)
+ // Compare strings instead of errors, as error stack is still different
+ suite.Require().Equal(
+ bankErr.Error(),
+ pbankErr.Error(),
+ "x/precisebank error should match x/bank BurnCoins error",
+ )
+ }
+
+ if tt.wantPanic != "" {
+ // First check the wantPanic string is correct.
+ // Actually specify the panic string in the test since it makes
+ // it more clear we are testing specific and different cases.
+ suite.Require().PanicsWithError(tt.wantPanic, func() {
+ _ = suite.BankKeeper.BurnCoins(suite.Ctx, tt.recipientModule, tt.burnAmount)
+ }, "expected panic error should match x/bank BurnCoins")
+
+ suite.Require().PanicsWithError(tt.wantPanic, func() {
+ _ = suite.Keeper.BurnCoins(suite.Ctx, tt.recipientModule, tt.burnAmount)
+ }, "x/precisebank panic should match x/bank BurnCoins")
+ }
+ })
+ }
+}
+
+func (suite *burnIntegrationTestSuite) TestBurnCoins() {
+ tests := []struct {
+ name string
+ startBalance sdk.Coins
+ burnCoins sdk.Coins
+ wantBalance sdk.Coins
+ wantErr string
+ }{
+ {
+ "passthrough - unrelated",
+ cs(c("meow", 1000)),
+ cs(c("meow", 1000)),
+ cs(),
+ "",
+ },
+ {
+ "passthrough - integer denom",
+ cs(c(types.IntegerCoinDenom, 2000)),
+ cs(c(types.IntegerCoinDenom, 1000)),
+ cs(c(types.ExtendedCoinDenom, 1000000000000000)),
+ "",
+ },
+ {
+ "fractional only - no borrow",
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ cs(c(types.ExtendedCoinDenom, 500)),
+ cs(c(types.ExtendedCoinDenom, 500)),
+ "",
+ },
+ {
+ "fractional burn - borrows",
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().AddRaw(100))),
+ cs(c(types.ExtendedCoinDenom, 500)),
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(400))),
+ "",
+ },
+ {
+ "error - insufficient integer balance",
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor())),
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(2))),
+ cs(),
+ // Returns correct error with akava balance (rewrites Bank BurnCoins err)
+ "spendable balance 1000000000000akava is smaller than 2000000000000akava: insufficient funds",
+ },
+ {
+ "error - insufficient fractional, borrow",
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ cs(c(types.ExtendedCoinDenom, 2000)),
+ cs(),
+ // Error from SendCoins to reserve
+ "spendable balance 1000akava is smaller than 2000akava: insufficient funds",
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ // Reset
+ suite.SetupTest()
+
+ moduleName := ibctransfertypes.ModuleName
+ recipientAddr := suite.AccountKeeper.GetModuleAddress(moduleName)
+
+ // Start balance
+ err := suite.Keeper.MintCoins(suite.Ctx, moduleName, tt.startBalance)
+ suite.Require().NoError(err)
+
+ // Burn
+ err = suite.Keeper.BurnCoins(suite.Ctx, moduleName, tt.burnCoins)
+ if tt.wantErr != "" {
+ suite.Require().Error(err)
+ suite.Require().EqualError(err, tt.wantErr)
+ return
+ }
+
+ suite.Require().NoError(err)
+
+ // -------------------------------------------------------------
+ // Check FULL balances
+ // x/bank balances + x/precisebank balance
+ // Exclude "ukava" as x/precisebank balance will include it
+ afterBalance := suite.GetAllBalances(recipientAddr)
+
+ suite.Require().Equal(
+ tt.wantBalance.String(),
+ afterBalance.String(),
+ "unexpected balance after minting %s to %s",
+ )
+
+ // Ensure reserve is backing all minted fractions
+ allInvariantsFn := keeper.AllInvariants(suite.Keeper)
+ res, stop := allInvariantsFn(suite.Ctx)
+ suite.Require().False(stop, "invariant should not be broken")
+ suite.Require().Empty(res, "unexpected invariant message: %s", res)
+
+ intCoinAmt := tt.burnCoins.AmountOf(types.IntegerCoinDenom).
+ Mul(types.ConversionFactor())
+
+ fraCoinAmt := tt.burnCoins.AmountOf(types.ExtendedCoinDenom)
+
+ totalExtCoinAmt := intCoinAmt.Add(fraCoinAmt)
+ spentCoins := sdk.NewCoins(sdk.NewCoin(
+ types.ExtendedCoinDenom,
+ totalExtCoinAmt,
+ ))
+
+ events := suite.Ctx.EventManager().Events()
+
+ expBurnEvent := banktypes.NewCoinBurnEvent(recipientAddr, spentCoins)
+ expSpendEvent := banktypes.NewCoinSpentEvent(recipientAddr, spentCoins)
+
+ if totalExtCoinAmt.IsZero() {
+ suite.Require().NotContains(events, expBurnEvent)
+ suite.Require().NotContains(events, expSpendEvent)
+ } else {
+ suite.Require().Contains(events, expBurnEvent)
+ suite.Require().Contains(events, expSpendEvent)
+ }
+ })
+ }
+}
+
+func (suite *burnIntegrationTestSuite) TestBurnCoins_Remainder() {
+ // This tests a series of small burns to ensure the remainder is both
+ // updated correctly and reserve is correctly updated. This only burns from
+ // 1 single account.
+
+ reserveAddr := suite.AccountKeeper.GetModuleAddress(types.ModuleName)
+
+ moduleName := ibctransfertypes.ModuleName
+ moduleAddr := suite.AccountKeeper.GetModuleAddress(moduleName)
+
+ startCoins := cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(5)))
+
+ // Start balance
+ err := suite.Keeper.MintCoins(
+ suite.Ctx,
+ moduleName,
+ startCoins,
+ )
+ suite.Require().NoError(err)
+
+ burnAmt := types.ConversionFactor().QuoRaw(10)
+ burnCoins := cs(ci(types.ExtendedCoinDenom, burnAmt))
+
+ // Burn 0.1 until balance is 0
+ for {
+ reserveBalBefore := suite.BankKeeper.GetBalance(
+ suite.Ctx,
+ reserveAddr,
+ types.IntegerCoinDenom,
+ )
+
+ balBefore := suite.Keeper.GetBalance(
+ suite.Ctx,
+ moduleAddr,
+ types.ExtendedCoinDenom,
+ )
+ remainderBefore := suite.Keeper.GetRemainderAmount(suite.Ctx)
+
+ // ----------------------------------------
+ // Burn
+ err := suite.Keeper.BurnCoins(
+ suite.Ctx,
+ moduleName,
+ burnCoins,
+ )
+ suite.Require().NoError(err)
+
+ // ----------------------------------------
+ // Checks
+ remainderAfter := suite.Keeper.GetRemainderAmount(suite.Ctx)
+ balAfter := suite.Keeper.GetBalance(
+ suite.Ctx,
+ moduleAddr,
+ types.ExtendedCoinDenom,
+ )
+ reserveBalAfter := suite.BankKeeper.GetBalance(
+ suite.Ctx,
+ reserveAddr,
+ types.IntegerCoinDenom,
+ )
+
+ suite.Require().Equal(
+ balBefore.Amount.Sub(burnAmt).String(),
+ balAfter.Amount.String(),
+ "balance should decrease by burn amount",
+ )
+
+ // Remainder should be updated correctly
+ suite.Require().Equal(
+ remainderBefore.Add(burnAmt).Mod(types.ConversionFactor()),
+ remainderAfter,
+ )
+
+ // If remainder has exceeded (then rolled over), reserve should be updated
+ if remainderAfter.LT(remainderBefore) {
+ suite.Require().Equal(
+ reserveBalBefore.Amount.SubRaw(1).String(),
+ reserveBalAfter.Amount.String(),
+ "reserve should decrease by 1 if remainder exceeds ConversionFactor",
+ )
+ }
+
+ // No more to burn
+ if balAfter.Amount.IsZero() {
+ break
+ }
+ }
+
+ // Run Invariants to ensure remainder is backing all fractions correctly
+ res, stop := keeper.AllInvariants(suite.Keeper)(suite.Ctx)
+ suite.Require().False(stop, "invariant should not be broken")
+ suite.Require().Empty(res, "unexpected invariant message: %s", res)
+}
+
+func (suite *burnIntegrationTestSuite) TestBurnCoins_Spread_Remainder() {
+ // This tests a series of small burns to ensure the remainder is both
+ // updated correctly and reserve is correctly updated. This burns from
+ // a series of multiple accounts, to test when the remainder is modified
+ // by multiple accounts.
+
+ reserveAddr := suite.AccountKeeper.GetModuleAddress(types.ModuleName)
+ burnerModuleName := ibctransfertypes.ModuleName
+ burnerAddr := suite.AccountKeeper.GetModuleAddress(burnerModuleName)
+
+ accCount := 20
+ startCoins := cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(5)))
+
+ addrs := []sdk.AccAddress{}
+
+ for i := 0; i < accCount; i++ {
+ addr := sdk.AccAddress(fmt.Sprintf("addr%d", i))
+ suite.MintToAccount(addr, startCoins)
+
+ addrs = append(addrs, addr)
+ }
+
+ burnAmt := types.ConversionFactor().QuoRaw(10)
+ burnCoins := cs(ci(types.ExtendedCoinDenom, burnAmt))
+
+ // Burn 0.1 from each account
+ for _, addr := range addrs {
+ reserveBalBefore := suite.BankKeeper.GetBalance(
+ suite.Ctx,
+ reserveAddr,
+ types.IntegerCoinDenom,
+ )
+
+ balBefore := suite.Keeper.GetBalance(
+ suite.Ctx,
+ addr,
+ types.ExtendedCoinDenom,
+ )
+ remainderBefore := suite.Keeper.GetRemainderAmount(suite.Ctx)
+
+ // ----------------------------------------
+ // Send & Burn
+ err := suite.Keeper.SendCoins(
+ suite.Ctx,
+ addr,
+ burnerAddr,
+ burnCoins,
+ )
+ suite.Require().NoError(err)
+
+ err = suite.Keeper.BurnCoins(
+ suite.Ctx,
+ burnerModuleName,
+ burnCoins,
+ )
+ suite.Require().NoError(err)
+
+ // ----------------------------------------
+ // Checks
+ remainderAfter := suite.Keeper.GetRemainderAmount(suite.Ctx)
+ balAfter := suite.Keeper.GetBalance(
+ suite.Ctx,
+ addr,
+ types.ExtendedCoinDenom,
+ )
+ reserveBalAfter := suite.BankKeeper.GetBalance(
+ suite.Ctx,
+ reserveAddr,
+ types.IntegerCoinDenom,
+ )
+
+ suite.Require().Equal(
+ balBefore.Amount.Sub(burnAmt).String(),
+ balAfter.Amount.String(),
+ "balance should decrease by burn amount",
+ )
+
+ // Remainder should be updated correctly
+ suite.Require().Equal(
+ remainderBefore.Add(burnAmt).Mod(types.ConversionFactor()),
+ remainderAfter,
+ )
+
+ suite.T().Logf("acc: %s", string(addr.Bytes()))
+ suite.T().Logf("acc bal: %s -> %s", balBefore, balAfter)
+ suite.T().Logf("remainder: %s -> %s", remainderBefore, remainderAfter)
+ suite.T().Logf("reserve: %v -> %v", reserveBalBefore, reserveBalAfter)
+
+ // Reserve will change when:
+ // 1. Account needs to borrow from integer (transfers to reserve)
+ // 2. Remainder meets or exceeds conversion factor (burn 1 from reserve)
+ reserveIncrease := sdkmath.ZeroInt()
+
+ // Does account need to borrow from integer?
+ if balBefore.Amount.Mod(types.ConversionFactor()).LT(burnAmt) {
+ reserveIncrease = reserveIncrease.AddRaw(1)
+ }
+
+ // If remainder has exceeded (then rolled over), burn additional 1
+ if remainderBefore.Add(burnAmt).GTE(types.ConversionFactor()) {
+ reserveIncrease = reserveIncrease.SubRaw(1)
+ }
+
+ suite.Require().Equal(
+ reserveBalBefore.Amount.Add(reserveIncrease).String(),
+ reserveBalAfter.Amount.String(),
+ "reserve should be updated by remainder and borrowing",
+ )
+
+ // Run Invariants to ensure remainder is backing all fractions correctly
+ res, stop := keeper.AllInvariants(suite.Keeper)(suite.Ctx)
+ suite.Require().False(stop, "invariant should not be broken")
+ suite.Require().Empty(res, "unexpected invariant message: %s", res)
+ }
+}
+
+func FuzzBurnCoins(f *testing.F) {
+ f.Add(int64(0))
+ f.Add(int64(100))
+ f.Add(types.ConversionFactor().Int64())
+ f.Add(types.ConversionFactor().MulRaw(5).Int64())
+ f.Add(types.ConversionFactor().MulRaw(2).AddRaw(123948723).Int64())
+
+ f.Fuzz(func(t *testing.T, amount int64) {
+ // No negative amounts
+ if amount < 0 {
+ amount = -amount
+ }
+
+ // Manually setup test suite since no direct Fuzz support in test suites
+ suite := new(burnIntegrationTestSuite)
+ suite.SetT(t)
+ suite.SetS(suite)
+ suite.SetupTest()
+
+ burnCount := int64(10)
+
+ // Has both mint & burn permissions
+ moduleName := ibctransfertypes.ModuleName
+ recipientAddr := suite.AccountKeeper.GetModuleAddress(moduleName)
+
+ // Start balance
+ err := suite.Keeper.MintCoins(
+ suite.Ctx,
+ moduleName,
+ cs(ci(types.ExtendedCoinDenom, sdkmath.NewInt(amount).MulRaw(burnCount))),
+ )
+ suite.Require().NoError(err)
+
+ // Burn multiple times to ensure different balance scenarios
+ for i := int64(0); i < burnCount; i++ {
+ err := suite.Keeper.BurnCoins(
+ suite.Ctx,
+ moduleName,
+ cs(c(types.ExtendedCoinDenom, amount)),
+ )
+ suite.Require().NoError(err)
+ }
+
+ // Check FULL balances
+ balAfter := suite.Keeper.GetBalance(suite.Ctx, recipientAddr, types.ExtendedCoinDenom)
+
+ suite.Require().Equalf(
+ int64(0),
+ balAfter.Amount.Int64(),
+ "all coins should be burned, got %d",
+ balAfter.Amount.Int64(),
+ )
+
+ // Run Invariants to ensure remainder is backing all fractions correctly
+ allInvariantsFn := keeper.AllInvariants(suite.Keeper)
+ res, stop := allInvariantsFn(suite.Ctx)
+ suite.Require().False(stop, "invariant should not be broken")
+ suite.Require().Empty(res, "unexpected invariant message: %s", res)
+ })
+}
diff --git a/x/precisebank/keeper/burn_test.go b/x/precisebank/keeper/burn_test.go
new file mode 100644
index 00000000..6d71c361
--- /dev/null
+++ b/x/precisebank/keeper/burn_test.go
@@ -0,0 +1,161 @@
+package keeper_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ "github.com/stretchr/testify/require"
+)
+
+// Testing module name for mocked GetModuleAccount()
+const burnerModuleName = "burner-module"
+
+func TestBurnCoins_PanicValidations(t *testing.T) {
+ // panic tests for invalid inputs
+
+ tests := []struct {
+ name string
+ recipientModule string
+ setupFn func(td testData)
+ burnAmount sdk.Coins
+ wantPanic string
+ }{
+ {
+ "invalid module",
+ "notamodule",
+ func(td testData) {
+ // Make module not found
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, "notamodule").
+ Return(nil).
+ Once()
+ },
+ cs(c("ukava", 1000)),
+ "module account notamodule does not exist: unknown address",
+ },
+ {
+ "no permission",
+ burnerModuleName,
+ func(td testData) {
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, burnerModuleName).
+ Return(authtypes.NewModuleAccount(
+ authtypes.NewBaseAccountWithAddress(sdk.AccAddress{1}),
+ burnerModuleName,
+ // no burn permission
+ )).
+ Once()
+ },
+ cs(c("ukava", 1000)),
+ fmt.Sprintf("module account %s does not have permissions to burn tokens: unauthorized", burnerModuleName),
+ },
+ {
+ "has burn permission",
+ burnerModuleName,
+ func(td testData) {
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, burnerModuleName).
+ Return(authtypes.NewModuleAccount(
+ authtypes.NewBaseAccountWithAddress(sdk.AccAddress{1}),
+ burnerModuleName,
+ // includes burner permission
+ authtypes.Burner,
+ )).
+ Once()
+
+ // Will call x/bank BurnCoins coins
+ td.bk.EXPECT().
+ BurnCoins(td.ctx, burnerModuleName, cs(c("ukava", 1000))).
+ Return(nil).
+ Once()
+ },
+ cs(c("ukava", 1000)),
+ "",
+ },
+ {
+ "disallow burning from x/precisebank",
+ types.ModuleName,
+ func(td testData) {
+ // No mock setup needed since this is checked before module
+ // account checks
+ },
+ cs(c("ukava", 1000)),
+ "module account precisebank cannot be burned from: unauthorized",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ td := NewMockedTestData(t)
+ tt.setupFn(td)
+
+ if tt.wantPanic != "" {
+ require.PanicsWithError(t, tt.wantPanic, func() {
+ _ = td.keeper.BurnCoins(td.ctx, tt.recipientModule, tt.burnAmount)
+ })
+ return
+ }
+
+ require.NotPanics(t, func() {
+ // Not testing errors, only panics for this test
+ _ = td.keeper.BurnCoins(td.ctx, tt.recipientModule, tt.burnAmount)
+ })
+ })
+ }
+}
+
+func TestBurnCoins_Errors(t *testing.T) {
+ // returned errors, not panics
+
+ tests := []struct {
+ name string
+ recipientModule string
+ setupFn func(td testData)
+ burnAmount sdk.Coins
+ wantError string
+ }{
+ {
+ "invalid coins",
+ burnerModuleName,
+ func(td testData) {
+ // Valid module account burner
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, burnerModuleName).
+ Return(authtypes.NewModuleAccount(
+ authtypes.NewBaseAccountWithAddress(sdk.AccAddress{1}),
+ burnerModuleName,
+ // includes burner permission
+ authtypes.Burner,
+ )).
+ Once()
+ },
+ sdk.Coins{sdk.Coin{
+ Denom: "ukava",
+ Amount: sdk.NewInt(-1000),
+ }},
+ "-1000ukava: invalid coins",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ td := NewMockedTestData(t)
+ tt.setupFn(td)
+
+ require.NotPanics(t, func() {
+ err := td.keeper.BurnCoins(td.ctx, tt.recipientModule, tt.burnAmount)
+
+ if tt.wantError != "" {
+ require.Error(t, err)
+ require.EqualError(t, err, tt.wantError)
+ return
+ }
+
+ require.NoError(t, err)
+ })
+ })
+ }
+}
diff --git a/x/precisebank/keeper/fractional_balance.go b/x/precisebank/keeper/fractional_balance.go
new file mode 100644
index 00000000..6eccdd86
--- /dev/null
+++ b/x/precisebank/keeper/fractional_balance.go
@@ -0,0 +1,109 @@
+package keeper
+
+import (
+ "errors"
+ "fmt"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/cosmos/cosmos-sdk/store/prefix"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+// GetFractionalBalance returns the fractional balance for an address.
+func (k *Keeper) GetFractionalBalance(
+ ctx sdk.Context,
+ address sdk.AccAddress,
+) sdkmath.Int {
+ store := prefix.NewStore(ctx.KVStore(k.storeKey), types.FractionalBalancePrefix)
+
+ bz := store.Get(types.FractionalBalanceKey(address))
+ if bz == nil {
+ return sdkmath.ZeroInt()
+ }
+
+ var bal sdkmath.Int
+ if err := bal.Unmarshal(bz); err != nil {
+ panic(fmt.Errorf("failed to unmarshal fractional balance: %w", err))
+ }
+
+ return bal
+}
+
+// SetFractionalBalance sets the fractional balance for an address.
+func (k *Keeper) SetFractionalBalance(
+ ctx sdk.Context,
+ address sdk.AccAddress,
+ amount sdkmath.Int,
+) {
+ if address.Empty() {
+ panic(errors.New("address cannot be empty"))
+ }
+
+ if amount.IsZero() {
+ k.DeleteFractionalBalance(ctx, address)
+ return
+ }
+
+ // Ensure the fractional balance is valid before setting it. Use the
+ // NewFractionalAmountFromInt wrapper to use its Validate() method.
+ if err := types.ValidateFractionalAmount(amount); err != nil {
+ panic(fmt.Errorf("amount is invalid: %w", err))
+ }
+
+ store := prefix.NewStore(ctx.KVStore(k.storeKey), types.FractionalBalancePrefix)
+
+ amountBytes, err := amount.Marshal()
+ if err != nil {
+ panic(fmt.Errorf("failed to marshal fractional balance: %w", err))
+ }
+
+ store.Set(types.FractionalBalanceKey(address), amountBytes)
+}
+
+// DeleteFractionalBalance deletes the fractional balance for an address.
+func (k *Keeper) DeleteFractionalBalance(
+ ctx sdk.Context,
+ address sdk.AccAddress,
+) {
+ store := prefix.NewStore(ctx.KVStore(k.storeKey), types.FractionalBalancePrefix)
+ store.Delete(types.FractionalBalanceKey(address))
+}
+
+// IterateFractionalBalances iterates over all fractional balances in the store
+// and performs a callback function.
+func (k *Keeper) IterateFractionalBalances(
+ ctx sdk.Context,
+ cb func(address sdk.AccAddress, amount sdkmath.Int) (stop bool),
+) {
+ store := prefix.NewStore(ctx.KVStore(k.storeKey), types.FractionalBalancePrefix)
+
+ iterator := store.Iterator(nil, nil)
+ defer iterator.Close()
+
+ for ; iterator.Valid(); iterator.Next() {
+ address := sdk.AccAddress(iterator.Key())
+
+ var amount sdkmath.Int
+ if err := amount.Unmarshal(iterator.Value()); err != nil {
+ panic(fmt.Errorf("failed to unmarshal fractional balance: %w", err))
+ }
+
+ if cb(address, amount) {
+ break
+ }
+ }
+}
+
+// GetTotalSumFractionalBalances returns the sum of all fractional balances.
+func (k *Keeper) GetTotalSumFractionalBalances(ctx sdk.Context) sdkmath.Int {
+ sum := sdkmath.ZeroInt()
+
+ k.IterateFractionalBalances(ctx, func(_ sdk.AccAddress, amount sdkmath.Int) bool {
+ sum = sum.Add(amount)
+ return false
+ })
+
+ return sum
+}
diff --git a/x/precisebank/keeper/fractional_balance_test.go b/x/precisebank/keeper/fractional_balance_test.go
new file mode 100644
index 00000000..c5521e1c
--- /dev/null
+++ b/x/precisebank/keeper/fractional_balance_test.go
@@ -0,0 +1,192 @@
+package keeper_test
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/cosmos/cosmos-sdk/store/prefix"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/stretchr/testify/require"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+func TestSetGetFractionalBalance(t *testing.T) {
+ addr := sdk.AccAddress([]byte("test-address"))
+
+ tests := []struct {
+ name string
+ address sdk.AccAddress
+ amount sdkmath.Int
+ setPanicMsg string
+ }{
+ {
+ "valid - min amount",
+ addr,
+ sdkmath.NewInt(1),
+ "",
+ },
+ {
+ "valid - positive amount",
+ addr,
+ sdkmath.NewInt(100),
+ "",
+ },
+ {
+ "valid - max amount",
+ addr,
+ types.ConversionFactor().SubRaw(1),
+ "",
+ },
+ {
+ "valid - zero amount (deletes)",
+ addr,
+ sdkmath.ZeroInt(),
+ "",
+ },
+ {
+ "invalid - negative amount",
+ addr,
+ sdkmath.NewInt(-1),
+ "amount is invalid: non-positive amount -1",
+ },
+ {
+ "invalid - over max amount",
+ addr,
+ types.ConversionFactor(),
+ "amount is invalid: amount 1000000000000 exceeds max of 999999999999",
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ td := NewMockedTestData(t)
+ ctx, k := td.ctx, td.keeper
+
+ if tt.setPanicMsg != "" {
+ require.PanicsWithError(t, tt.setPanicMsg, func() {
+ k.SetFractionalBalance(ctx, tt.address, tt.amount)
+ })
+
+ return
+ }
+
+ require.NotPanics(t, func() {
+ k.SetFractionalBalance(ctx, tt.address, tt.amount)
+ })
+
+ // If its zero balance, check it was deleted in store
+ if tt.amount.IsZero() {
+ store := prefix.NewStore(ctx.KVStore(td.storeKey), types.FractionalBalancePrefix)
+ bz := store.Get(types.FractionalBalanceKey(tt.address))
+ require.Nil(t, bz)
+
+ return
+ }
+
+ gotAmount := k.GetFractionalBalance(ctx, tt.address)
+ require.Equal(t, tt.amount, gotAmount)
+
+ // Delete balance
+ k.DeleteFractionalBalance(ctx, tt.address)
+
+ store := prefix.NewStore(ctx.KVStore(td.storeKey), types.FractionalBalancePrefix)
+ bz := store.Get(types.FractionalBalanceKey(tt.address))
+ require.Nil(t, bz)
+ })
+ }
+}
+
+func TestSetFractionalBalance_InvalidAddr(t *testing.T) {
+ tk := NewMockedTestData(t)
+ ctx, k := tk.ctx, tk.keeper
+
+ require.PanicsWithError(
+ t,
+ "address cannot be empty",
+ func() {
+ k.SetFractionalBalance(ctx, sdk.AccAddress{}, sdkmath.NewInt(100))
+ },
+ "setting balance with empty address should panic",
+ )
+}
+
+func TestSetFractionalBalance_ZeroDeletes(t *testing.T) {
+ td := NewMockedTestData(t)
+ ctx, k := td.ctx, td.keeper
+
+ addr := sdk.AccAddress([]byte("test-address"))
+
+ // Set balance
+ k.SetFractionalBalance(ctx, addr, sdkmath.NewInt(100))
+
+ bal := k.GetFractionalBalance(ctx, addr)
+ require.Equal(t, sdkmath.NewInt(100), bal)
+
+ // Set zero balance
+ k.SetFractionalBalance(ctx, addr, sdkmath.ZeroInt())
+
+ // Check balance was deleted
+ store := prefix.NewStore(ctx.KVStore(td.storeKey), types.FractionalBalancePrefix)
+ bz := store.Get(types.FractionalBalanceKey(addr))
+ require.Nil(t, bz)
+
+ // Set zero balance again on non-existent balance
+ require.NotPanics(
+ t,
+ func() {
+ k.SetFractionalBalance(ctx, addr, sdkmath.ZeroInt())
+ },
+ "deleting non-existent balance should not panic",
+ )
+}
+
+func TestIterateFractionalBalances(t *testing.T) {
+ tk := NewMockedTestData(t)
+ ctx, k := tk.ctx, tk.keeper
+
+ addrs := []sdk.AccAddress{}
+
+ for i := 1; i < 10; i++ {
+ addr := sdk.AccAddress([]byte{byte(i)})
+ addrs = append(addrs, addr)
+
+ // Set balance same as their address byte
+ k.SetFractionalBalance(ctx, addr, sdkmath.NewInt(int64(i)))
+ }
+
+ seenAddrs := []sdk.AccAddress{}
+
+ k.IterateFractionalBalances(ctx, func(addr sdk.AccAddress, bal sdkmath.Int) bool {
+ seenAddrs = append(seenAddrs, addr)
+
+ // Balance is same as first address byte
+ require.Equal(t, int64(addr.Bytes()[0]), bal.Int64())
+
+ return false
+ })
+
+ require.ElementsMatch(t, addrs, seenAddrs, "all addresses should be seen")
+}
+
+func TestGetAggregateSumFractionalBalances(t *testing.T) {
+ tk := NewMockedTestData(t)
+ ctx, k := tk.ctx, tk.keeper
+
+ // Set balances from 1 to 10
+ sum := sdkmath.ZeroInt()
+ for i := 1; i < 10; i++ {
+ addr := sdk.AccAddress([]byte{byte(i)})
+ amt := sdkmath.NewInt(int64(i))
+
+ sum = sum.Add(amt)
+
+ require.NotPanics(t, func() {
+ k.SetFractionalBalance(ctx, addr, amt)
+ })
+ }
+
+ gotSum := k.GetTotalSumFractionalBalances(ctx)
+ require.Equal(t, sum, gotSum)
+}
diff --git a/x/precisebank/keeper/grpc_query.go b/x/precisebank/keeper/grpc_query.go
new file mode 100644
index 00000000..9a00d8e8
--- /dev/null
+++ b/x/precisebank/keeper/grpc_query.go
@@ -0,0 +1,73 @@
+package keeper
+
+import (
+ "context"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+type queryServer struct {
+ keeper Keeper
+}
+
+// NewQueryServerImpl creates a new server for handling gRPC queries.
+func NewQueryServerImpl(k Keeper) types.QueryServer {
+ return &queryServer{keeper: k}
+}
+
+var _ types.QueryServer = queryServer{}
+
+// TotalFractionalBalances returns the total sum of all fractional balances.
+// This is mostly for external verification of the total fractional balances,
+// being a multiple of the conversion factor and backed by the reserve.
+func (s queryServer) TotalFractionalBalances(
+ goCtx context.Context,
+ req *types.QueryTotalFractionalBalancesRequest,
+) (*types.QueryTotalFractionalBalancesResponse, error) {
+ ctx := sdk.UnwrapSDKContext(goCtx)
+
+ totalAmount := s.keeper.GetTotalSumFractionalBalances(ctx)
+
+ totalCoin := sdk.NewCoin(types.ExtendedCoinDenom, totalAmount)
+
+ return &types.QueryTotalFractionalBalancesResponse{
+ Total: totalCoin,
+ }, nil
+}
+
+// Remainder returns the remainder amount in x/precisebank.
+func (s queryServer) Remainder(
+ goCtx context.Context,
+ req *types.QueryRemainderRequest,
+) (*types.QueryRemainderResponse, error) {
+ ctx := sdk.UnwrapSDKContext(goCtx)
+
+ remainder := s.keeper.GetRemainderAmount(ctx)
+ remainderCoin := sdk.NewCoin(types.ExtendedCoinDenom, remainder)
+
+ return &types.QueryRemainderResponse{
+ Remainder: remainderCoin,
+ }, nil
+}
+
+// FractionalBalance returns the fractional balance of an account.
+func (s queryServer) FractionalBalance(
+ goCtx context.Context,
+ req *types.QueryFractionalBalanceRequest,
+) (*types.QueryFractionalBalanceResponse, error) {
+ ctx := sdk.UnwrapSDKContext(goCtx)
+
+ address, err := sdk.AccAddressFromBech32(req.Address)
+ if err != nil {
+ return nil, err
+ }
+
+ amt := s.keeper.GetFractionalBalance(ctx, address)
+ fractionalBalance := sdk.NewCoin(types.ExtendedCoinDenom, amt)
+
+ return &types.QueryFractionalBalanceResponse{
+ FractionalBalance: fractionalBalance,
+ }, nil
+}
diff --git a/x/precisebank/keeper/grpc_query_test.go b/x/precisebank/keeper/grpc_query_test.go
new file mode 100644
index 00000000..1ab99e94
--- /dev/null
+++ b/x/precisebank/keeper/grpc_query_test.go
@@ -0,0 +1,171 @@
+package keeper_test
+
+import (
+ "context"
+ "strconv"
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/cosmos/cosmos-sdk/baseapp"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
+ "github.com/stretchr/testify/suite"
+
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/testutil"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+type grpcQueryTestSuite struct {
+ testutil.Suite
+
+ queryClient types.QueryClient
+}
+
+func (suite *grpcQueryTestSuite) SetupTest() {
+ suite.Suite.SetupTest()
+
+ queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry())
+ types.RegisterQueryServer(queryHelper, keeper.NewQueryServerImpl(suite.Keeper))
+
+ suite.queryClient = types.NewQueryClient(queryHelper)
+}
+
+func TestGrpcQueryTestSuite(t *testing.T) {
+ suite.Run(t, new(grpcQueryTestSuite))
+}
+
+func (suite *grpcQueryTestSuite) TestQueryTotalFractionalBalance() {
+ testCases := []struct {
+ name string
+ giveBalances []sdkmath.Int
+ }{
+ {
+ "empty",
+ []sdkmath.Int{},
+ },
+ {
+ "min amount",
+ []sdkmath.Int{
+ types.ConversionFactor().QuoRaw(2),
+ types.ConversionFactor().QuoRaw(2),
+ },
+ },
+ {
+ "exceeds conversion factor",
+ []sdkmath.Int{
+ // 4 accounts * 0.5 == 2
+ types.ConversionFactor().QuoRaw(2),
+ types.ConversionFactor().QuoRaw(2),
+ types.ConversionFactor().QuoRaw(2),
+ types.ConversionFactor().QuoRaw(2),
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ suite.Run(tc.name, func() {
+ suite.SetupTest()
+
+ total := sdk.NewCoin(types.ExtendedCoinDenom, sdkmath.ZeroInt())
+ for i, balance := range tc.giveBalances {
+ addr := sdk.AccAddress([]byte(strconv.Itoa(i)))
+ suite.Keeper.SetFractionalBalance(suite.Ctx, addr, balance)
+
+ total.Amount = total.Amount.Add(balance)
+ }
+
+ res, err := suite.queryClient.TotalFractionalBalances(
+ context.Background(),
+ &types.QueryTotalFractionalBalancesRequest{},
+ )
+ suite.Require().NoError(err)
+
+ suite.Require().Equal(total, res.Total)
+ })
+ }
+}
+
+func (suite *grpcQueryTestSuite) TestQueryRemainder() {
+ res, err := suite.queryClient.Remainder(
+ context.Background(),
+ &types.QueryRemainderRequest{},
+ )
+ suite.Require().NoError(err)
+
+ expRemainder := sdk.NewCoin(types.ExtendedCoinDenom, sdkmath.ZeroInt())
+ suite.Require().Equal(expRemainder, res.Remainder)
+
+ // Mint fractional coins to create non-zero remainder
+
+ pbk := suite.App.GetPrecisebankKeeper()
+
+ coin := sdk.NewCoin(types.ExtendedCoinDenom, sdkmath.OneInt())
+ err = pbk.MintCoins(
+ suite.Ctx,
+ minttypes.ModuleName,
+ sdk.NewCoins(coin),
+ )
+ suite.Require().NoError(err)
+
+ res, err = suite.queryClient.Remainder(
+ context.Background(),
+ &types.QueryRemainderRequest{},
+ )
+ suite.Require().NoError(err)
+
+ expRemainder.Amount = types.ConversionFactor().Sub(coin.Amount)
+ suite.Require().Equal(expRemainder, res.Remainder)
+}
+
+func (suite *grpcQueryTestSuite) TestQueryFractionalBalance() {
+ testCases := []struct {
+ name string
+ giveBalance sdkmath.Int
+ }{
+ {
+ "zero",
+ sdkmath.ZeroInt(),
+ },
+ {
+ "min amount",
+ sdkmath.OneInt(),
+ },
+ {
+ "max amount",
+ types.ConversionFactor().SubRaw(1),
+ },
+ {
+ "multiple integer amounts, 0 fractional",
+ types.ConversionFactor().MulRaw(5),
+ },
+ {
+ "multiple integer amounts, non-zero fractional",
+ types.ConversionFactor().MulRaw(5).Add(types.ConversionFactor().QuoRaw(2)),
+ },
+ }
+
+ for _, tc := range testCases {
+ suite.Run(tc.name, func() {
+ suite.SetupTest()
+
+ addr := sdk.AccAddress([]byte("test"))
+
+ coin := sdk.NewCoin(types.ExtendedCoinDenom, tc.giveBalance)
+ suite.MintToAccount(addr, sdk.NewCoins(coin))
+
+ res, err := suite.queryClient.FractionalBalance(
+ context.Background(),
+ &types.QueryFractionalBalanceRequest{
+ Address: addr.String(),
+ },
+ )
+ suite.Require().NoError(err)
+
+ // Only fractional amount, even if minted more than conversion factor
+ expAmount := tc.giveBalance.Mod(types.ConversionFactor())
+ expFractionalBalance := sdk.NewCoin(types.ExtendedCoinDenom, expAmount)
+ suite.Require().Equal(expFractionalBalance, res.FractionalBalance)
+ })
+ }
+}
diff --git a/x/precisebank/keeper/invariants.go b/x/precisebank/keeper/invariants.go
new file mode 100644
index 00000000..41e79097
--- /dev/null
+++ b/x/precisebank/keeper/invariants.go
@@ -0,0 +1,205 @@
+package keeper
+
+import (
+ "fmt"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+// RegisterInvariants registers the x/precisebank module invariants
+func RegisterInvariants(
+ ir sdk.InvariantRegistry,
+ k Keeper,
+ bk types.BankKeeper,
+) {
+ ir.RegisterRoute(types.ModuleName, "reserve-backs-fractions", ReserveBacksFractionsInvariant(k))
+ ir.RegisterRoute(types.ModuleName, "balance-remainder-total", BalancedFractionalTotalInvariant(k))
+ ir.RegisterRoute(types.ModuleName, "valid-fractional-balances", ValidFractionalAmountsInvariant(k))
+ ir.RegisterRoute(types.ModuleName, "valid-remainder-amount", ValidRemainderAmountInvariant(k))
+ ir.RegisterRoute(types.ModuleName, "fractional-denom-not-in-bank", FractionalDenomNotInBankInvariant(k))
+}
+
+// AllInvariants runs all invariants of the X/precisebank module.
+func AllInvariants(k Keeper) sdk.Invariant {
+ return func(ctx sdk.Context) (string, bool) {
+ res, stop := ReserveBacksFractionsInvariant(k)(ctx)
+ if stop {
+ return res, stop
+ }
+
+ res, stop = BalancedFractionalTotalInvariant(k)(ctx)
+ if stop {
+ return res, stop
+ }
+
+ res, stop = ValidFractionalAmountsInvariant(k)(ctx)
+ if stop {
+ return res, stop
+ }
+
+ res, stop = ValidRemainderAmountInvariant(k)(ctx)
+ if stop {
+ return res, stop
+ }
+
+ res, stop = FractionalDenomNotInBankInvariant(k)(ctx)
+ if stop {
+ return res, stop
+ }
+
+ return "", false
+ }
+}
+
+// ReserveBacksFractionsInvariant checks that the total amount of backing
+// coins in the reserve is equal to the total amount of fractional balances,
+// such that the backing is always available to redeem all fractional balances
+// and there are no extra coins in the reserve that are not backing any
+// fractional balances.
+func ReserveBacksFractionsInvariant(k Keeper) sdk.Invariant {
+ return func(ctx sdk.Context) (string, bool) {
+ var (
+ msg string
+ broken bool
+ )
+
+ fractionalBalSum := k.GetTotalSumFractionalBalances(ctx)
+ remainderAmount := k.GetRemainderAmount(ctx)
+
+ // Get the total amount of backing coins in the reserve
+ moduleAddr := k.ak.GetModuleAddress(types.ModuleName)
+ reserveIntegerBalance := k.bk.GetBalance(ctx, moduleAddr, types.IntegerCoinDenom)
+ reserveExtendedBalance := reserveIntegerBalance.Amount.Mul(types.ConversionFactor())
+
+ // The total amount of backing coins in the reserve should be equal to
+ // fractional balances + remainder amount
+ totalRequiredBacking := fractionalBalSum.Add(remainderAmount)
+
+ broken = !reserveExtendedBalance.Equal(totalRequiredBacking)
+ msg = fmt.Sprintf(
+ "%s reserve balance %s mismatches %s (fractional balances %s + remainder %s)\n",
+ types.ExtendedCoinDenom,
+ reserveExtendedBalance,
+ totalRequiredBacking,
+ fractionalBalSum,
+ remainderAmount,
+ )
+
+ return sdk.FormatInvariant(
+ types.ModuleName, "module reserve backing total fractional balances",
+ msg,
+ ), broken
+ }
+}
+
+// ValidFractionalAmountsInvariant checks that all individual fractional
+// balances are valid.
+func ValidFractionalAmountsInvariant(k Keeper) sdk.Invariant {
+ return func(ctx sdk.Context) (string, bool) {
+ var (
+ msg string
+ count int
+ )
+
+ k.IterateFractionalBalances(ctx, func(addr sdk.AccAddress, amount sdkmath.Int) bool {
+ if err := types.ValidateFractionalAmount(amount); err != nil {
+ count++
+ msg += fmt.Sprintf("\t%s has an invalid fractional amount of %s\n", addr, amount)
+ }
+
+ return false
+ })
+
+ broken := count != 0
+
+ return sdk.FormatInvariant(
+ types.ModuleName, "valid-fractional-balances",
+ fmt.Sprintf("amount of invalid fractional balances found %d\n%s", count, msg),
+ ), broken
+ }
+}
+
+// ValidRemainderAmountInvariant checks that the remainder amount is valid.
+func ValidRemainderAmountInvariant(k Keeper) sdk.Invariant {
+ return func(ctx sdk.Context) (string, bool) {
+ var (
+ msg string
+ broken bool
+ )
+
+ remainderAmount := k.GetRemainderAmount(ctx)
+
+ if !remainderAmount.IsZero() {
+ // Only validate if non-zero, as zero is default value
+ if err := types.ValidateFractionalAmount(remainderAmount); err != nil {
+ broken = true
+ msg = fmt.Sprintf("remainder amount is invalid: %s", err)
+ }
+ }
+
+ return sdk.FormatInvariant(
+ types.ModuleName, "valid-remainder-amount",
+ msg,
+ ), broken
+ }
+}
+
+// BalancedFractionalTotalInvariant checks that the sum of fractional balances
+// and the remainder amount is divisible by the conversion factor without any
+// leftover amount.
+func BalancedFractionalTotalInvariant(k Keeper) sdk.Invariant {
+ return func(ctx sdk.Context) (string, bool) {
+ fractionalBalSum := k.GetTotalSumFractionalBalances(ctx)
+ remainderAmount := k.GetRemainderAmount(ctx)
+
+ total := fractionalBalSum.Add(remainderAmount)
+ fractionalAmount := total.Mod(types.ConversionFactor())
+
+ broken := false
+ msg := ""
+
+ if !fractionalAmount.IsZero() {
+ broken = true
+ msg = fmt.Sprintf(
+ "(sum(FractionalBalances) + remainder) %% conversionFactor should be 0 but got %v",
+ fractionalAmount,
+ )
+ }
+
+ return sdk.FormatInvariant(
+ types.ModuleName, "balance-remainder-total",
+ msg,
+ ), broken
+ }
+}
+
+// FractionalDenomNotInBankInvariant checks that the bank does not hold any
+// fractional denoms. These assets, e.g. akava, should only exist in the
+// x/precisebank module as this is a decimal extension of ukava that shares
+// the same total supply and is effectively the same asset. ukava held by this
+// module in x/bank backs all fractional balances in x/precisebank. If akava
+// somehow ends up in x/bank, then it would both break all expectations of this
+// module as well as be double-counted in the total supply.
+func FractionalDenomNotInBankInvariant(k Keeper) sdk.Invariant {
+ return func(ctx sdk.Context) (string, bool) {
+ extBankSupply := k.bk.GetSupply(ctx, types.ExtendedCoinDenom)
+
+ broken := !extBankSupply.IsZero()
+ msg := ""
+
+ if broken {
+ msg = fmt.Sprintf(
+ "x/bank should not hold any %v but has supply of %v",
+ types.ExtendedCoinDenom,
+ extBankSupply,
+ )
+ }
+
+ return sdk.FormatInvariant(
+ types.ModuleName, "fractional-denom-not-in-bank",
+ msg,
+ ), broken
+ }
+}
diff --git a/x/precisebank/keeper/invariants_integration_test.go b/x/precisebank/keeper/invariants_integration_test.go
new file mode 100644
index 00000000..29d50269
--- /dev/null
+++ b/x/precisebank/keeper/invariants_integration_test.go
@@ -0,0 +1,129 @@
+package keeper_test
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/testutil"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/stretchr/testify/suite"
+)
+
+type invariantsIntegrationTestSuite struct {
+ testutil.Suite
+}
+
+func (suite *invariantsIntegrationTestSuite) SetupTest() {
+ suite.Suite.SetupTest()
+}
+
+func TestInvariantsIntegrationTest(t *testing.T) {
+ suite.Run(t, new(invariantsIntegrationTestSuite))
+}
+
+func (suite *invariantsIntegrationTestSuite) FundReserve(amt sdkmath.Int) {
+ coins := sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, amt))
+ err := suite.BankKeeper.MintCoins(suite.Ctx, types.ModuleName, coins)
+ suite.Require().NoError(err)
+}
+
+func (suite *invariantsIntegrationTestSuite) TestReserveBackingFractionalInvariant() {
+ tests := []struct {
+ name string
+ setupFn func(ctx sdk.Context, k keeper.Keeper)
+ wantBroken bool
+ wantMsg string
+ }{
+ {
+ "valid - empty state",
+ func(_ sdk.Context, _ keeper.Keeper) {},
+ false,
+ "",
+ },
+ {
+ "valid - fractional balances, no remainder",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
+ k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2))
+ // 1 integer backs same amount fractional
+ suite.FundReserve(sdk.NewInt(1))
+ },
+ false,
+ "",
+ },
+ {
+ "valid - fractional balances, with remainder",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
+ k.SetRemainderAmount(ctx, types.ConversionFactor().QuoRaw(2))
+ // 1 integer backs same amount fractional including remainder
+ suite.FundReserve(sdk.NewInt(1))
+ },
+ false,
+ "",
+ },
+ {
+ "invalid - no fractional balances, non-zero remainder",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ k.SetRemainderAmount(ctx, types.ConversionFactor().QuoRaw(2))
+ },
+ true,
+ "precisebank: module reserve backing total fractional balances invariant\nakava reserve balance 0 mismatches 500000000000 (fractional balances 0 + remainder 500000000000)\n\n",
+ },
+ {
+ "invalid - insufficient reserve backing",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ amt := types.ConversionFactor().QuoRaw(2)
+
+ // 0.5 int coins x 4
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, amt)
+ k.SetFractionalBalance(ctx, sdk.AccAddress{2}, amt)
+ k.SetFractionalBalance(ctx, sdk.AccAddress{3}, amt)
+ k.SetRemainderAmount(ctx, amt)
+
+ // Needs 2 to back 0.5 x 4
+ suite.FundReserve(sdk.NewInt(1))
+ },
+ true,
+ "precisebank: module reserve backing total fractional balances invariant\nakava reserve balance 1000000000000 mismatches 2000000000000 (fractional balances 1500000000000 + remainder 500000000000)\n\n",
+ },
+ {
+ "invalid - excess reserve backing",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ amt := types.ConversionFactor().QuoRaw(2)
+
+ // 0.5 int coins x 4
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, amt)
+ k.SetFractionalBalance(ctx, sdk.AccAddress{2}, amt)
+ k.SetFractionalBalance(ctx, sdk.AccAddress{3}, amt)
+ k.SetRemainderAmount(ctx, amt)
+
+ // Needs 2 to back 0.5 x 4
+ suite.FundReserve(sdk.NewInt(3))
+ },
+ true,
+ "precisebank: module reserve backing total fractional balances invariant\nakava reserve balance 3000000000000 mismatches 2000000000000 (fractional balances 1500000000000 + remainder 500000000000)\n\n",
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ // Reset each time
+ suite.SetupTest()
+
+ tt.setupFn(suite.Ctx, suite.Keeper)
+
+ invariantFn := keeper.ReserveBacksFractionsInvariant(suite.Keeper)
+ msg, broken := invariantFn(suite.Ctx)
+
+ if tt.wantBroken {
+ suite.Require().True(broken, "invariant should be broken but is not")
+ suite.Require().Equal(tt.wantMsg, msg)
+ } else {
+ suite.Require().Falsef(broken, "invariant should not be broken but is: %s", msg)
+ }
+ })
+ }
+}
diff --git a/x/precisebank/keeper/invariants_test.go b/x/precisebank/keeper/invariants_test.go
new file mode 100644
index 00000000..9aa086e3
--- /dev/null
+++ b/x/precisebank/keeper/invariants_test.go
@@ -0,0 +1,215 @@
+package keeper_test
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/cosmos/cosmos-sdk/store/prefix"
+ storetypes "github.com/cosmos/cosmos-sdk/store/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ "github.com/0glabs/0g-chain/x/precisebank/types/mocks"
+ "github.com/stretchr/testify/require"
+)
+
+func TestBalancedFractionalTotalInvariant(t *testing.T) {
+ tests := []struct {
+ name string
+ setupFn func(ctx sdk.Context, k keeper.Keeper)
+ wantBroken bool
+ wantMsg string
+ }{
+ {
+ "valid - empty state",
+ func(_ sdk.Context, _ keeper.Keeper) {},
+ false,
+ "",
+ },
+ {
+ "valid - balances, 0 remainder",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
+ k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2))
+ },
+ false,
+ "",
+ },
+ {
+ "valid - balances, non-zero remainder",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
+ k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2).SubRaw(1))
+
+ k.SetRemainderAmount(ctx, sdkmath.OneInt())
+ },
+ false,
+ "",
+ },
+ {
+ "invalid - balances, 0 remainder",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
+ k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2).SubRaw(1))
+ },
+ true,
+ "precisebank: balance-remainder-total invariant\n(sum(FractionalBalances) + remainder) % conversionFactor should be 0 but got 999999999999\n",
+ },
+ {
+ "invalid - invalid balances, non-zero (insufficient) remainder",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
+ k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2).SubRaw(2))
+ k.SetRemainderAmount(ctx, sdkmath.OneInt())
+ },
+ true,
+ "precisebank: balance-remainder-total invariant\n(sum(FractionalBalances) + remainder) % conversionFactor should be 0 but got 999999999999\n",
+ },
+ {
+ "invalid - invalid balances, non-zero (excess) remainder",
+ func(ctx sdk.Context, k keeper.Keeper) {
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
+ k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2).SubRaw(2))
+ k.SetRemainderAmount(ctx, sdkmath.NewInt(5))
+ },
+ true,
+ "precisebank: balance-remainder-total invariant\n(sum(FractionalBalances) + remainder) % conversionFactor should be 0 but got 3\n",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Reset each time
+ td := NewMockedTestData(t)
+
+ tt.setupFn(td.ctx, td.keeper)
+
+ invariantFn := keeper.BalancedFractionalTotalInvariant(td.keeper)
+ msg, broken := invariantFn(td.ctx)
+
+ if tt.wantBroken {
+ require.True(t, broken, "invariant should be broken but is not")
+ require.Equal(t, tt.wantMsg, msg)
+ } else {
+ require.False(t, broken, "invariant should not be broken but is")
+ }
+ })
+ }
+}
+
+func TestValidFractionalAmountsInvariant(t *testing.T) {
+ tests := []struct {
+ name string
+ setupFn func(ctx sdk.Context, k keeper.Keeper, storeKey storetypes.StoreKey)
+ wantBroken bool
+ wantMsg string
+ }{
+ {
+ "valid - empty state",
+ func(_ sdk.Context, _ keeper.Keeper, _ storetypes.StoreKey) {},
+ false,
+ "",
+ },
+ {
+ "valid - valid balances",
+ func(ctx sdk.Context, k keeper.Keeper, _ storetypes.StoreKey) {
+ k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
+ k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2))
+ },
+ false,
+ "",
+ },
+ {
+ "invalid - exceeds max balance",
+ func(ctx sdk.Context, _ keeper.Keeper, storeKey storetypes.StoreKey) {
+ // Requires manual store manipulation so it is unlikely to have
+ // invalid state in practice. SetFractionalBalance will validate
+ // before setting.
+ addr := sdk.AccAddress{1}
+ amount := types.ConversionFactor()
+
+ store := prefix.NewStore(ctx.KVStore(storeKey), types.FractionalBalancePrefix)
+
+ amountBytes, err := amount.Marshal()
+ require.NoError(t, err)
+
+ store.Set(types.FractionalBalanceKey(addr), amountBytes)
+ },
+ true,
+ "precisebank: valid-fractional-balances invariant\namount of invalid fractional balances found 1\n\tkava1qy0xn7za has an invalid fractional amount of 1000000000000\n\n",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Reset each time
+ td := NewMockedTestData(t)
+
+ tt.setupFn(td.ctx, td.keeper, td.storeKey)
+
+ invariantFn := keeper.ValidFractionalAmountsInvariant(td.keeper)
+ msg, broken := invariantFn(td.ctx)
+
+ if tt.wantBroken {
+ require.True(t, broken, "invariant should be broken but is not")
+ require.Equal(t, tt.wantMsg, msg)
+ } else {
+ require.False(t, broken, "invariant should not be broken but is")
+ }
+ })
+ }
+}
+
+func TestFractionalDenomNotInBankInvariant(t *testing.T) {
+ tests := []struct {
+ name string
+ setupFn func(ctx sdk.Context, bk *mocks.MockBankKeeper)
+ wantBroken bool
+ wantMsg string
+ }{
+ {
+ "valid - integer denom (ukava) supply",
+ func(ctx sdk.Context, bk *mocks.MockBankKeeper) {
+ // No fractional balance in x/bank
+ // This also enforces there is no GetSupply() call for IntegerCoinDenom / ukava
+ bk.EXPECT().
+ GetSupply(ctx, types.ExtendedCoinDenom).
+ Return(sdk.NewCoin(types.ExtendedCoinDenom, sdkmath.ZeroInt())).
+ Once()
+ },
+ false,
+ "",
+ },
+ {
+ "invalid - x/bank contains fractional denom (akava)",
+ func(ctx sdk.Context, bk *mocks.MockBankKeeper) {
+ bk.EXPECT().
+ GetSupply(ctx, types.ExtendedCoinDenom).
+ Return(sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000))).
+ Once()
+ },
+ true,
+ "precisebank: fractional-denom-not-in-bank invariant\nx/bank should not hold any akava but has supply of 1000akava\n",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Reset each time
+ td := NewMockedTestData(t)
+
+ tt.setupFn(td.ctx, td.bk)
+
+ invariantFn := keeper.FractionalDenomNotInBankInvariant(td.keeper)
+ msg, broken := invariantFn(td.ctx)
+
+ if tt.wantBroken {
+ require.True(t, broken, "invariant should be broken but is not")
+ require.Equal(t, tt.wantMsg, msg)
+ } else {
+ require.False(t, broken, "invariant should not be broken but is")
+ }
+ })
+ }
+}
diff --git a/x/precisebank/keeper/keeper.go b/x/precisebank/keeper/keeper.go
new file mode 100644
index 00000000..d7a52900
--- /dev/null
+++ b/x/precisebank/keeper/keeper.go
@@ -0,0 +1,37 @@
+package keeper
+
+import (
+ "github.com/cosmos/cosmos-sdk/codec"
+ storetypes "github.com/cosmos/cosmos-sdk/store/types"
+
+ evmtypes "github.com/evmos/ethermint/x/evm/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+// Enforce that Keeper implements the expected keeper interfaces
+var _ evmtypes.BankKeeper = Keeper{}
+
+// Keeper defines the precisebank module's keeper
+type Keeper struct {
+ cdc codec.BinaryCodec
+ storeKey storetypes.StoreKey
+
+ bk types.BankKeeper
+ ak types.AccountKeeper
+}
+
+// NewKeeper creates a new keeper
+func NewKeeper(
+ cdc codec.BinaryCodec,
+ storeKey storetypes.StoreKey,
+ bk types.BankKeeper,
+ ak types.AccountKeeper,
+) Keeper {
+ return Keeper{
+ cdc: cdc,
+ storeKey: storeKey,
+ bk: bk,
+ ak: ak,
+ }
+}
diff --git a/x/precisebank/keeper/keeper_test.go b/x/precisebank/keeper/keeper_test.go
new file mode 100644
index 00000000..6bdadf0b
--- /dev/null
+++ b/x/precisebank/keeper/keeper_test.go
@@ -0,0 +1,53 @@
+package keeper_test
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ storetypes "github.com/cosmos/cosmos-sdk/store/types"
+ "github.com/cosmos/cosmos-sdk/testutil"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+
+ "github.com/0glabs/0g-chain/app"
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ "github.com/0glabs/0g-chain/x/precisebank/types/mocks"
+)
+
+// testData defines necessary fields for testing keeper store methods and mocks
+// for unit tests without full app setup.
+type testData struct {
+ ctx sdk.Context
+ keeper keeper.Keeper
+ storeKey *storetypes.KVStoreKey
+ bk *mocks.MockBankKeeper
+ ak *mocks.MockAccountKeeper
+}
+
+// NewMockedTestData creates a new testData instance with mocked bank and
+// account keepers.
+func NewMockedTestData(t *testing.T) testData {
+ storeKey := sdk.NewKVStoreKey(types.ModuleName)
+ // Not required by module, but needs to be non-nil for context
+ tKey := sdk.NewTransientStoreKey("transient_test")
+ ctx := testutil.DefaultContext(storeKey, tKey)
+
+ bk := mocks.NewMockBankKeeper(t)
+ ak := mocks.NewMockAccountKeeper(t)
+
+ tApp := app.NewTestApp()
+ cdc := tApp.AppCodec()
+ k := keeper.NewKeeper(cdc, storeKey, bk, ak)
+
+ return testData{
+ ctx: ctx,
+ keeper: k,
+ storeKey: storeKey,
+ bk: bk,
+ ak: ak,
+ }
+}
+
+func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
+func ci(denom string, amount sdkmath.Int) sdk.Coin { return sdk.NewCoin(denom, amount) }
+func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
diff --git a/x/precisebank/keeper/mint.go b/x/precisebank/keeper/mint.go
new file mode 100644
index 00000000..76c1d7c7
--- /dev/null
+++ b/x/precisebank/keeper/mint.go
@@ -0,0 +1,212 @@
+package keeper
+
+import (
+ "fmt"
+
+ errorsmod "cosmossdk.io/errors"
+ sdkmath "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+// MintCoins creates new coins from thin air and adds it to the module account.
+// If ExtendedCoinDenom is provided, the corresponding fractional amount is
+// added to the module state.
+// It will panic if the module account does not exist or is unauthorized.
+func (k Keeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error {
+ // Disallow minting to x/precisebank module
+ if moduleName == types.ModuleName {
+ panic(errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "module account %s cannot be minted to", moduleName))
+ }
+
+ // Note: MintingRestrictionFn is not used in x/precisebank
+ // Panic errors are identical to x/bank for consistency.
+ acc := k.ak.GetModuleAccount(ctx, moduleName)
+ if acc == nil {
+ panic(errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName))
+ }
+
+ if !acc.HasPermission(authtypes.Minter) {
+ panic(errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to mint tokens", moduleName))
+ }
+
+ // Ensure the coins are valid before minting
+ if !amt.IsValid() {
+ return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
+ }
+
+ // Get non-ExtendedCoinDenom coins
+ passthroughCoins := amt
+
+ extendedAmount := amt.AmountOf(types.ExtendedCoinDenom)
+ if extendedAmount.IsPositive() {
+ // Remove ExtendedCoinDenom from the coins as it is managed by x/precisebank
+ removeCoin := sdk.NewCoin(types.ExtendedCoinDenom, extendedAmount)
+ passthroughCoins = amt.Sub(removeCoin)
+ }
+
+ // Coins unmanaged by x/precisebank are passed through to x/bank
+ if !passthroughCoins.Empty() {
+ if err := k.bk.MintCoins(ctx, moduleName, passthroughCoins); err != nil {
+ return err
+ }
+ }
+
+ // Only mint extended coin if the amount is positive
+ if extendedAmount.IsPositive() {
+ if err := k.mintExtendedCoin(ctx, moduleName, extendedAmount); err != nil {
+ return err
+ }
+ }
+
+ fullEmissionCoins := sdk.NewCoins(types.SumExtendedCoin(amt))
+ if fullEmissionCoins.IsZero() {
+ return nil
+ }
+
+ ctx.EventManager().EmitEvents(sdk.Events{
+ banktypes.NewCoinMintEvent(acc.GetAddress(), fullEmissionCoins),
+ banktypes.NewCoinReceivedEvent(acc.GetAddress(), fullEmissionCoins),
+ })
+
+ return nil
+}
+
+// mintExtendedCoin manages the minting of only extended coins. This also
+// handles integer carry over from fractional balance to integer balance if
+// necessary depending on the fractional balance and minting amount. Ensures
+// that the reserve fully backs the additional minted amount, minting any extra
+// reserve integer coins if necessary.
+// 4 Cases:
+// 1. NO integer carry over, >= 0 remainder - no reserve mint
+// 2. NO integer carry over, negative remainder - mint 1 to reserve
+// 3. Integer carry over, >= 0 remainder
+// - Transfer 1 integer from reserve -> account
+//
+// 4. Integer carry over, negative remainder
+// - Transfer 1 integer from reserve -> account
+// - Mint 1 to reserve
+// Optimization:
+// - Increase direct account mint amount by 1, no extra reserve mint
+func (k Keeper) mintExtendedCoin(
+ ctx sdk.Context,
+ recipientModuleName string,
+ amt sdkmath.Int,
+) error {
+ moduleAddr := k.ak.GetModuleAddress(recipientModuleName)
+
+ // Get current module account fractional balance - 0 if not found
+ fractionalAmount := k.GetFractionalBalance(ctx, moduleAddr)
+
+ // Get separated mint amounts
+ integerMintAmount := amt.Quo(types.ConversionFactor())
+ fractionalMintAmount := amt.Mod(types.ConversionFactor())
+
+ // Get previous remainder amount, as we need to it before carry calculation
+ // for the optimization path.
+ prevRemainder := k.GetRemainderAmount(ctx)
+
+ // Deduct new remainder with minted fractional amount. This will result in
+ // two cases:
+ // 1. Zero or positive remainder: remainder is sufficient to back the minted
+ // fractional amount. Reserve is also sufficient to back the minted amount
+ // so no additional reserve integer coin is needed.
+ // 2. Negative remainder: remainder is insufficient to back the minted
+ // fractional amount. Reserve will need to be increased to back the mint
+ // amount.
+ newRemainder := prevRemainder.Sub(fractionalMintAmount)
+
+ // Get new fractional balance after minting, this could be greater than
+ // the conversion factor and must be checked for carry over to integer mint
+ // amount as being set as-is may cause fractional balance exceeding max.
+ newFractionalBalance := fractionalAmount.Add(fractionalMintAmount)
+
+ // Case #3 - Integer carry, remainder is sufficient (0 or positive)
+ if newFractionalBalance.GTE(types.ConversionFactor()) && newRemainder.GTE(sdkmath.ZeroInt()) {
+ // Carry should send from reserve -> account, instead of minting an
+ // extra integer coin. Otherwise doing an extra mint will require a burn
+ // from reserves to maintain exact backing.
+ carryCoin := sdk.NewCoin(types.IntegerCoinDenom, sdkmath.OneInt())
+
+ // SendCoinsFromModuleToModule allows for sending coins even if the
+ // recipient module account is blocked.
+ if err := k.bk.SendCoinsFromModuleToModule(
+ ctx,
+ types.ModuleName,
+ recipientModuleName,
+ sdk.NewCoins(carryCoin),
+ ); err != nil {
+ return err
+ }
+ }
+
+ // Case #4 - Integer carry, remainder is insufficient
+ // This is the optimization path where the integer mint amount is increased
+ // by 1, instead of doing both a reserve -> account transfer and reserve mint.
+ if newFractionalBalance.GTE(types.ConversionFactor()) && newRemainder.IsNegative() {
+ integerMintAmount = integerMintAmount.AddRaw(1)
+ }
+
+ // If it carries over, adjust the fractional balance to account for the
+ // previously added 1 integer amount.
+ // fractional amounts x and y where both x and y < ConversionFactor
+ // x + y < (2 * ConversionFactor) - 2
+ // x + y < 1 integer amount + fractional amount
+ if newFractionalBalance.GTE(types.ConversionFactor()) {
+ // Subtract 1 integer equivalent amount of fractional balance. Same
+ // behavior as using .Mod() in this case.
+ newFractionalBalance = newFractionalBalance.Sub(types.ConversionFactor())
+ }
+
+ // Mint new integer amounts in x/bank - including carry over from fractional
+ // amount if any.
+ if integerMintAmount.IsPositive() {
+ integerMintCoin := sdk.NewCoin(types.IntegerCoinDenom, integerMintAmount)
+
+ if err := k.bk.MintCoins(
+ ctx,
+ recipientModuleName,
+ sdk.NewCoins(integerMintCoin),
+ ); err != nil {
+ return err
+ }
+ }
+
+ // Assign new fractional balance in x/precisebank
+ k.SetFractionalBalance(ctx, moduleAddr, newFractionalBalance)
+
+ // ----------------------------------------
+ // Update remainder & reserves to back minted fractional coins
+
+ // Mint an additional reserve integer coin if remainder is insufficient.
+ // The remainder is the amount of fractional coins that can be minted and
+ // still be fully backed by reserve. If the remainder is less than the
+ // minted fractional amount, then the reserve needs to be increased to
+ // back the additional fractional amount.
+ // Optimization: This is only done when the integer amount does NOT carry,
+ // as a direct account mint is done instead of integer carry transfer +
+ // insufficient remainder reserve mint.
+ wasCarried := fractionalAmount.Add(fractionalMintAmount).GTE(types.ConversionFactor())
+ if prevRemainder.LT(fractionalMintAmount) && !wasCarried {
+ // Always only 1 integer coin, as fractionalMintAmount < ConversionFactor
+ reserveMintCoins := sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdkmath.OneInt()))
+ if err := k.bk.MintCoins(ctx, types.ModuleName, reserveMintCoins); err != nil {
+ return fmt.Errorf("failed to mint %s for reserve: %w", reserveMintCoins, err)
+ }
+ }
+
+ // newRemainder will be negative if prevRemainder < fractionalMintAmount.
+ // This needs to be adjusted back to the corresponding positive value. The
+ // remainder will be always < conversionFactor after add if it is negative.
+ if newRemainder.IsNegative() {
+ newRemainder = newRemainder.Add(types.ConversionFactor())
+ }
+
+ k.SetRemainderAmount(ctx, newRemainder)
+
+ return nil
+}
diff --git a/x/precisebank/keeper/mint_integration_test.go b/x/precisebank/keeper/mint_integration_test.go
new file mode 100644
index 00000000..67880138
--- /dev/null
+++ b/x/precisebank/keeper/mint_integration_test.go
@@ -0,0 +1,424 @@
+package keeper_test
+
+import (
+ "fmt"
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
+ banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
+ minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/testutil"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ "github.com/stretchr/testify/suite"
+)
+
+type mintIntegrationTestSuite struct {
+ testutil.Suite
+}
+
+func (suite *mintIntegrationTestSuite) SetupTest() {
+ suite.Suite.SetupTest()
+}
+
+func TestMintIntegrationTest(t *testing.T) {
+ suite.Run(t, new(mintIntegrationTestSuite))
+}
+
+func (suite *mintIntegrationTestSuite) TestBlockedRecipient() {
+ // Tests that sending funds to x/precisebank is disallowed.
+ // x/precisebank balance is used as the reserve funds and should not be
+ // directly interacted with by external modules or users.
+ msgServer := bankkeeper.NewMsgServerImpl(suite.BankKeeper)
+
+ fromAddr := sdk.AccAddress{1}
+
+ // To x/precisebank
+ toAddr := suite.AccountKeeper.GetModuleAddress(types.ModuleName)
+ amount := cs(c("ukava", 1000))
+
+ msg := banktypes.NewMsgSend(fromAddr, toAddr, amount)
+
+ _, err := msgServer.Send(sdk.WrapSDKContext(suite.Ctx), msg)
+ suite.Require().Error(err)
+
+ suite.Require().EqualError(
+ err,
+ fmt.Sprintf("%s is not allowed to receive funds: unauthorized", toAddr.String()),
+ )
+}
+
+func (suite *mintIntegrationTestSuite) TestMintCoins_MatchingErrors() {
+ // x/precisebank MintCoins should be identical to x/bank MintCoins to
+ // consumers. This test ensures that the panics & errors returned by
+ // x/precisebank are identical to x/bank.
+
+ tests := []struct {
+ name string
+ recipientModule string
+ mintAmount sdk.Coins
+ wantErr string
+ wantPanic string
+ }{
+ {
+ "invalid module",
+ "notamodule",
+ cs(c("ukava", 1000)),
+ "",
+ "module account notamodule does not exist: unknown address",
+ },
+ {
+ "no mint permissions",
+ // Check app.go to ensure this module has no mint permissions
+ authtypes.FeeCollectorName,
+ cs(c("ukava", 1000)),
+ "",
+ "module account fee_collector does not have permissions to mint tokens: unauthorized",
+ },
+ {
+ "invalid amount",
+ minttypes.ModuleName,
+ sdk.Coins{sdk.Coin{Denom: "ukava", Amount: sdkmath.NewInt(-100)}},
+ "-100ukava: invalid coins",
+ "",
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ // Reset
+ suite.SetupTest()
+
+ if tt.wantErr == "" && tt.wantPanic == "" {
+ suite.Fail("test must specify either wantErr or wantPanic")
+ }
+
+ if tt.wantErr != "" {
+ // Check x/bank MintCoins for identical error
+ bankErr := suite.BankKeeper.MintCoins(suite.Ctx, tt.recipientModule, tt.mintAmount)
+ suite.Require().Error(bankErr)
+ suite.Require().EqualError(bankErr, tt.wantErr, "expected error should match x/bank MintCoins error")
+
+ pbankErr := suite.Keeper.MintCoins(suite.Ctx, tt.recipientModule, tt.mintAmount)
+ suite.Require().Error(pbankErr)
+ // Compare strings instead of errors, as error stack is still different
+ suite.Require().Equal(
+ bankErr.Error(),
+ pbankErr.Error(),
+ "x/precisebank error should match x/bank MintCoins error",
+ )
+ }
+
+ if tt.wantPanic != "" {
+ // First check the wantPanic string is correct.
+ // Actually specify the panic string in the test since it makes
+ // it more clear we are testing specific and different cases.
+ suite.Require().PanicsWithError(tt.wantPanic, func() {
+ _ = suite.BankKeeper.MintCoins(suite.Ctx, tt.recipientModule, tt.mintAmount)
+ }, "expected panic error should match x/bank MintCoins")
+
+ suite.Require().PanicsWithError(tt.wantPanic, func() {
+ _ = suite.Keeper.MintCoins(suite.Ctx, tt.recipientModule, tt.mintAmount)
+ }, "x/precisebank panic should match x/bank MintCoins")
+ }
+ })
+ }
+}
+
+func (suite *mintIntegrationTestSuite) TestMintCoins() {
+ type mintTest struct {
+ mintAmount sdk.Coins
+ // Expected **full** balances after MintCoins(mintAmount)
+ wantBalance sdk.Coins
+ }
+
+ tests := []struct {
+ name string
+ recipientModule string
+ // Instead of having a start balance, we just have a list of mints to
+ // both test & get into desired non-default states.
+ mints []mintTest
+ }{
+ {
+ "passthrough - unrelated",
+ minttypes.ModuleName,
+ []mintTest{
+ {
+ mintAmount: cs(c("busd", 1000)),
+ wantBalance: cs(c("busd", 1000)),
+ },
+ },
+ },
+ {
+ "passthrough - integer denom",
+ minttypes.ModuleName,
+ []mintTest{
+ {
+ mintAmount: cs(c(types.IntegerCoinDenom, 1000)),
+ wantBalance: cs(c(types.ExtendedCoinDenom, 1000000000000000)),
+ },
+ },
+ },
+ {
+ "fractional only",
+ minttypes.ModuleName,
+ []mintTest{
+ {
+ mintAmount: cs(c(types.ExtendedCoinDenom, 1000)),
+ wantBalance: cs(c(types.ExtendedCoinDenom, 1000)),
+ },
+ {
+ mintAmount: cs(c(types.ExtendedCoinDenom, 1000)),
+ wantBalance: cs(c(types.ExtendedCoinDenom, 2000)),
+ },
+ },
+ },
+ {
+ "fractional only with carry",
+ minttypes.ModuleName,
+ []mintTest{
+ {
+ // Start with (1/4 * 3) = 0.75
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().QuoRaw(4).MulRaw(3))),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().QuoRaw(4).MulRaw(3))),
+ },
+ {
+ // Add another 0.50 to incur carry to test reserve on carry
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().QuoRaw(2))),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().QuoRaw(4).MulRaw(5))),
+ },
+ },
+ },
+ {
+ "fractional only, resulting in exact carry and 0 remainder",
+ minttypes.ModuleName,
+ []mintTest{
+ // mint 0.5, acc = 0.5, reserve = 1
+ {
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().QuoRaw(2))),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().QuoRaw(2))),
+ },
+ // mint another 0.5, acc = 1, reserve = 0
+ // Reserve actually goes down by 1 for integer carry
+ {
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().QuoRaw(2))),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor())),
+ },
+ },
+ },
+ {
+ "exact carry",
+ minttypes.ModuleName,
+ []mintTest{
+ {
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor())),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor())),
+ },
+ // Carry again - exact amount
+ {
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor())),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(2))),
+ },
+ },
+ },
+ {
+ "carry with extra",
+ minttypes.ModuleName,
+ []mintTest{
+ // MintCoins(C + 100)
+ {
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().AddRaw(100))),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().AddRaw(100))),
+ },
+ // MintCoins(C + 5), total = 2C + 105
+ {
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().AddRaw(5))),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(2).AddRaw(105))),
+ },
+ },
+ },
+ {
+ "integer with fractional",
+ minttypes.ModuleName,
+ []mintTest{
+ {
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(5).AddRaw(100))),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(5).AddRaw(100))),
+ },
+ {
+ mintAmount: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(2).AddRaw(5))),
+ wantBalance: cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(7).AddRaw(105))),
+ },
+ },
+ },
+ {
+ "with passthrough",
+ minttypes.ModuleName,
+ []mintTest{
+ {
+ mintAmount: cs(
+ ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(5).AddRaw(100)),
+ c("busd", 1000),
+ ),
+ wantBalance: cs(
+ ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(5).AddRaw(100)),
+ c("busd", 1000),
+ ),
+ },
+ {
+ mintAmount: cs(
+ ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(2).AddRaw(5)),
+ c("meow", 40),
+ ),
+ wantBalance: cs(
+ ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(7).AddRaw(105)),
+ c("busd", 1000),
+ c("meow", 40),
+ ),
+ },
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ // Reset
+ suite.SetupTest()
+
+ recipientAddr := suite.AccountKeeper.GetModuleAddress(tt.recipientModule)
+
+ for _, mt := range tt.mints {
+ err := suite.Keeper.MintCoins(suite.Ctx, tt.recipientModule, mt.mintAmount)
+ suite.Require().NoError(err)
+
+ // -------------------------------------------------------------
+ // Check FULL balances
+ // x/bank balances + x/precisebank balance
+ // Exclude "ukava" as x/precisebank balance will include it
+ bankCoins := suite.BankKeeper.GetAllBalances(suite.Ctx, recipientAddr)
+
+ // Only use x/bank balances for non-ukava denoms
+ var denoms []string
+ for _, coin := range bankCoins {
+ // Ignore integer coins, query the extended denom instead
+ if coin.Denom == types.IntegerCoinDenom {
+ continue
+ }
+
+ denoms = append(denoms, coin.Denom)
+ }
+
+ // Add the extended denom to the list of denoms to balance check
+ // Will be included in balance check even if x/bank doesn't have
+ // ukava.
+ denoms = append(denoms, types.ExtendedCoinDenom)
+
+ // All balance queries through x/precisebank
+ afterBalance := sdk.NewCoins()
+ for _, denom := range denoms {
+ coin := suite.Keeper.GetBalance(suite.Ctx, recipientAddr, denom)
+ afterBalance = afterBalance.Add(coin)
+ }
+
+ suite.Require().Equal(
+ mt.wantBalance.String(),
+ afterBalance.String(),
+ "unexpected balance after minting %s to %s",
+ )
+
+ // Ensure reserve is backing all minted fractions
+ allInvariantsFn := keeper.AllInvariants(suite.Keeper)
+ msg, stop := allInvariantsFn(suite.Ctx)
+ suite.Require().Falsef(stop, "invariant should not be broken: %s", msg)
+
+ // Get event for minted coins
+ intCoinAmt := mt.mintAmount.AmountOf(types.IntegerCoinDenom).
+ Mul(types.ConversionFactor())
+
+ fraCoinAmt := mt.mintAmount.AmountOf(types.ExtendedCoinDenom)
+
+ totalExtCoinAmt := intCoinAmt.Add(fraCoinAmt)
+ extCoins := sdk.NewCoins(sdk.NewCoin(types.ExtendedCoinDenom, totalExtCoinAmt))
+
+ // Check for mint event
+ events := suite.Ctx.EventManager().Events()
+
+ expMintEvent := banktypes.NewCoinMintEvent(
+ recipientAddr,
+ extCoins,
+ )
+
+ expReceivedEvent := banktypes.NewCoinReceivedEvent(
+ recipientAddr,
+ extCoins,
+ )
+
+ if totalExtCoinAmt.IsZero() {
+ suite.Require().NotContains(events, expMintEvent)
+ suite.Require().NotContains(events, expReceivedEvent)
+ } else {
+ suite.Require().Contains(events, expMintEvent)
+ suite.Require().Contains(events, expReceivedEvent)
+ }
+ }
+ })
+ }
+}
+
+func FuzzMintCoins(f *testing.F) {
+ f.Add(int64(0))
+ f.Add(int64(100))
+ f.Add(types.ConversionFactor().Int64())
+ f.Add(types.ConversionFactor().QuoRaw(2).Int64())
+ f.Add(types.ConversionFactor().MulRaw(5).Int64())
+ f.Add(types.ConversionFactor().MulRaw(2).AddRaw(123948723).Int64())
+
+ f.Fuzz(func(t *testing.T, amount int64) {
+ // No negative amounts
+ if amount < 0 {
+ amount = -amount
+ }
+
+ // Manually setup test suite since no direct Fuzz support in test suites
+ suite := new(mintIntegrationTestSuite)
+ suite.SetT(t)
+ suite.SetS(suite)
+ suite.SetupTest()
+
+ mintCount := int64(10)
+
+ suite.T().Logf("minting %d %d times", amount, mintCount)
+
+ // Mint 10 times to include mints from non-zero balances
+ for i := int64(0); i < mintCount; i++ {
+ err := suite.Keeper.MintCoins(
+ suite.Ctx,
+ minttypes.ModuleName,
+ cs(c(types.ExtendedCoinDenom, amount)),
+ )
+ suite.Require().NoError(err)
+ }
+
+ // Check FULL balances
+ recipientAddr := suite.AccountKeeper.GetModuleAddress(minttypes.ModuleName)
+ bal := suite.Keeper.GetBalance(suite.Ctx, recipientAddr, types.ExtendedCoinDenom)
+
+ suite.Require().Equalf(
+ amount*mintCount,
+ bal.Amount.Int64(),
+ "unexpected balance after minting %d %d times",
+ amount,
+ mintCount,
+ )
+
+ // Run Invariants to ensure remainder is backing all minted fractions
+ // and in a valid state
+ res, stop := keeper.AllInvariants(suite.Keeper)(suite.Ctx)
+ suite.False(stop, "invariant should not be broken")
+ suite.Empty(res, "unexpected invariant message")
+ })
+}
diff --git a/x/precisebank/keeper/mint_test.go b/x/precisebank/keeper/mint_test.go
new file mode 100644
index 00000000..d2619d83
--- /dev/null
+++ b/x/precisebank/keeper/mint_test.go
@@ -0,0 +1,366 @@
+package keeper_test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ sdkmath "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+func TestMintCoins_PanicValidations(t *testing.T) {
+ // panic tests for invalid inputs
+
+ tests := []struct {
+ name string
+ recipientModule string
+ setupFn func(td testData)
+ mintAmount sdk.Coins
+ wantPanic string
+ }{
+ {
+ "invalid module",
+ "notamodule",
+ func(td testData) {
+ // Make module not found
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, "notamodule").
+ Return(nil).
+ Once()
+ },
+ cs(c("ukava", 1000)),
+ "module account notamodule does not exist: unknown address",
+ },
+ {
+ "no permission",
+ minttypes.ModuleName,
+ func(td testData) {
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, minttypes.ModuleName).
+ Return(authtypes.NewModuleAccount(
+ authtypes.NewBaseAccountWithAddress(sdk.AccAddress{1}),
+ minttypes.ModuleName,
+ // no mint permission
+ )).
+ Once()
+ },
+ cs(c("ukava", 1000)),
+ "module account mint does not have permissions to mint tokens: unauthorized",
+ },
+ {
+ "has mint permission",
+ minttypes.ModuleName,
+ func(td testData) {
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, minttypes.ModuleName).
+ Return(authtypes.NewModuleAccount(
+ authtypes.NewBaseAccountWithAddress(sdk.AccAddress{1}),
+ minttypes.ModuleName,
+ // includes minter permission
+ authtypes.Minter,
+ )).
+ Once()
+
+ // Will call x/bank MintCoins coins
+ td.bk.EXPECT().
+ MintCoins(td.ctx, minttypes.ModuleName, cs(c("ukava", 1000))).
+ Return(nil).
+ Once()
+ },
+ cs(c("ukava", 1000)),
+ "",
+ },
+ {
+ "disallow minting to x/precisebank",
+ types.ModuleName,
+ func(td testData) {
+ // No mock setup needed since this is checked before module
+ // account checks
+ },
+ cs(c("ukava", 1000)),
+ "module account precisebank cannot be minted to: unauthorized",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ td := NewMockedTestData(t)
+ tt.setupFn(td)
+
+ if tt.wantPanic != "" {
+ require.PanicsWithError(t, tt.wantPanic, func() {
+ _ = td.keeper.MintCoins(td.ctx, tt.recipientModule, tt.mintAmount)
+ })
+ return
+ }
+
+ require.NotPanics(t, func() {
+ // Not testing errors, only panics for this test
+ _ = td.keeper.MintCoins(td.ctx, tt.recipientModule, tt.mintAmount)
+ })
+ })
+ }
+}
+
+func TestMintCoins_Errors(t *testing.T) {
+ // returned errors, not panics
+
+ tests := []struct {
+ name string
+ recipientModule string
+ setupFn func(td testData)
+ mintAmount sdk.Coins
+ wantError string
+ }{
+ {
+ "invalid coins",
+ minttypes.ModuleName,
+ func(td testData) {
+ // Valid module account minter
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, minttypes.ModuleName).
+ Return(authtypes.NewModuleAccount(
+ authtypes.NewBaseAccountWithAddress(sdk.AccAddress{1}),
+ minttypes.ModuleName,
+ // includes minter permission
+ authtypes.Minter,
+ )).
+ Once()
+ },
+ sdk.Coins{sdk.Coin{
+ Denom: "ukava",
+ Amount: sdk.NewInt(-1000),
+ }},
+ "-1000ukava: invalid coins",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ td := NewMockedTestData(t)
+ tt.setupFn(td)
+
+ require.NotPanics(t, func() {
+ err := td.keeper.MintCoins(td.ctx, tt.recipientModule, tt.mintAmount)
+
+ if tt.wantError != "" {
+ require.Error(t, err)
+ require.EqualError(t, err, tt.wantError)
+ return
+ }
+
+ require.NoError(t, err)
+ })
+ })
+ }
+}
+
+func TestMintCoins_ExpectedCalls(t *testing.T) {
+ // Tests the expected calls to the bank keeper when minting coins
+
+ tests := []struct {
+ name string
+ // Only care about starting fractional balance.
+ // MintCoins() doesn't care about the previous integer balance.
+ startFractionalBalance sdkmath.Int
+ mintAmount sdk.Coins
+ // account x/precisebank balance (fractional amount)
+ wantPreciseBalance sdkmath.Int
+ }{
+ {
+ "passthrough mint - integer denom",
+ sdkmath.ZeroInt(),
+ cs(c("ukava", 1000)),
+ sdkmath.ZeroInt(),
+ },
+
+ {
+ "passthrough mint - unrelated denom",
+ sdkmath.ZeroInt(),
+ cs(c("meow", 1000)),
+ sdkmath.ZeroInt(),
+ },
+ {
+ "no carry - 0 starting fractional",
+ sdkmath.ZeroInt(),
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ sdkmath.NewInt(1000),
+ },
+ {
+ "no carry - non-zero fractional",
+ sdkmath.NewInt(1_000_000),
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ sdkmath.NewInt(1_001_000),
+ },
+ {
+ "fractional carry",
+ // max fractional amount
+ types.ConversionFactor().SubRaw(1),
+ cs(c(types.ExtendedCoinDenom, 1)), // +1 to carry
+ sdkmath.ZeroInt(),
+ },
+ {
+ "fractional carry max",
+ // max fractional amount + max fractional amount
+ types.ConversionFactor().SubRaw(1),
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(1))),
+ types.ConversionFactor().SubRaw(2),
+ },
+ {
+ "integer with fractional no carry",
+ sdkmath.NewInt(1234),
+ // mint 100 fractional
+ cs(c(types.ExtendedCoinDenom, 100)),
+ sdkmath.NewInt(1234 + 100),
+ },
+ {
+ "integer with fractional carry",
+ types.ConversionFactor().SubRaw(100),
+ // mint 105 fractional to carry
+ cs(c(types.ExtendedCoinDenom, 105)),
+ sdkmath.NewInt(5),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ td := NewMockedTestData(t)
+
+ // Set initial fractional balance
+ // Initial integer balance doesn't matter for this test
+ moduleAddr := sdk.AccAddress{1}
+ td.keeper.SetFractionalBalance(
+ td.ctx,
+ moduleAddr,
+ tt.startFractionalBalance,
+ )
+ fBal := td.keeper.GetFractionalBalance(td.ctx, moduleAddr)
+ require.Equal(t, tt.startFractionalBalance, fBal)
+
+ // Always calls GetModuleAccount() to check if module exists &
+ // has permission
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, minttypes.ModuleName).
+ Return(authtypes.NewModuleAccount(
+ authtypes.NewBaseAccountWithAddress(
+ moduleAddr,
+ ),
+ minttypes.ModuleName,
+ // Include minter permissions - not testing permission in
+ // this test
+ authtypes.Minter,
+ )).
+ Once()
+
+ // ----------------------------------------
+ // Separate passthrough and extended coins
+ // Determine how much is passed through to x/bank
+ passthroughCoins := tt.mintAmount
+
+ found, extCoins := tt.mintAmount.Find(types.ExtendedCoinDenom)
+ if found {
+ // Remove extended coin from passthrough coins
+ passthroughCoins = passthroughCoins.Sub(extCoins)
+ } else {
+ extCoins = sdk.NewCoin(types.ExtendedCoinDenom, sdkmath.ZeroInt())
+ }
+
+ require.Equalf(
+ t,
+ sdkmath.ZeroInt(),
+ passthroughCoins.AmountOf(types.ExtendedCoinDenom),
+ "expected pass through coins should not include %v",
+ types.ExtendedCoinDenom,
+ )
+
+ // ----------------------------------------
+ // Set expectations for minting passthrough coins
+ // Only expect MintCoins to be called with passthrough coins with non-zero amount
+ if !passthroughCoins.Empty() {
+ t.Logf("Expecting MintCoins(%v)", passthroughCoins)
+
+ td.bk.EXPECT().
+ MintCoins(td.ctx, minttypes.ModuleName, passthroughCoins).
+ Return(nil).
+ Once()
+ }
+
+ // ----------------------------------------
+ // Set expectations for reserve minting when fractional amounts
+ // are minted & remainder is insufficient
+ mintFractionalAmount := extCoins.Amount.Mod(types.ConversionFactor())
+ currentRemainder := td.keeper.GetRemainderAmount(td.ctx)
+
+ causesIntegerCarry := fBal.Add(mintFractionalAmount).GTE(types.ConversionFactor())
+ remainderEnough := currentRemainder.GTE(mintFractionalAmount)
+
+ // Optimization: Carry & insufficient remainder is directly minted
+ if causesIntegerCarry && !remainderEnough {
+ extCoins = extCoins.AddAmount(types.ConversionFactor())
+ }
+
+ // ----------------------------------------
+ // Set expectations for minting fractional coins
+ if !extCoins.IsNil() && extCoins.IsPositive() {
+ td.ak.EXPECT().
+ GetModuleAddress(minttypes.ModuleName).
+ Return(moduleAddr).
+ Once()
+
+ // Initial integer balance is always 0 for this test
+ mintIntegerAmount := extCoins.Amount.Quo(types.ConversionFactor())
+
+ // Minted coins does NOT include roll-over, simply excludes
+ mintCoins := cs(ci(types.IntegerCoinDenom, mintIntegerAmount))
+
+ // Only expect MintCoins to be called with mint coins with
+ // non-zero amount.
+ // Will fail if x/bank MintCoins is called with empty coins
+ if !mintCoins.Empty() {
+ t.Logf("Expecting MintCoins(%v)", mintCoins)
+
+ td.bk.EXPECT().
+ MintCoins(td.ctx, minttypes.ModuleName, mintCoins).
+ Return(nil).
+ Once()
+ }
+ }
+
+ if causesIntegerCarry && remainderEnough {
+ td.bk.EXPECT().
+ SendCoinsFromModuleToModule(
+ td.ctx,
+ types.ModuleName,
+ minttypes.ModuleName,
+ cs(c(types.IntegerCoinDenom, 1)),
+ ).
+ Return(nil).
+ Once()
+ }
+
+ if !remainderEnough && !causesIntegerCarry {
+ reserveMintCoins := cs(c(types.IntegerCoinDenom, 1))
+ td.bk.EXPECT().
+ // Mints to x/precisebank
+ MintCoins(td.ctx, types.ModuleName, reserveMintCoins).
+ Return(nil).
+ Once()
+ }
+
+ // ----------------------------------------
+ // Actual call after all setup and expectations
+ require.NotPanics(t, func() {
+ err := td.keeper.MintCoins(td.ctx, minttypes.ModuleName, tt.mintAmount)
+ require.NoError(t, err)
+ })
+
+ // Check final fractional balance
+ fBal = td.keeper.GetFractionalBalance(td.ctx, moduleAddr)
+ require.Equal(t, tt.wantPreciseBalance, fBal)
+ })
+ }
+}
diff --git a/x/precisebank/keeper/remainder_amount.go b/x/precisebank/keeper/remainder_amount.go
new file mode 100644
index 00000000..32ae2fdf
--- /dev/null
+++ b/x/precisebank/keeper/remainder_amount.go
@@ -0,0 +1,66 @@
+package keeper
+
+import (
+ "fmt"
+
+ sdkmath "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+// GetRemainderAmount returns the internal remainder amount.
+func (k *Keeper) GetRemainderAmount(
+ ctx sdk.Context,
+) sdkmath.Int {
+ store := ctx.KVStore(k.storeKey)
+
+ bz := store.Get(types.RemainderBalanceKey)
+ if bz == nil {
+ return sdkmath.ZeroInt()
+ }
+
+ var bal sdkmath.Int
+ if err := bal.Unmarshal(bz); err != nil {
+ panic(fmt.Errorf("failed to unmarshal remainder amount: %w", err))
+ }
+
+ return bal
+}
+
+// SetRemainderAmount sets the internal remainder amount.
+func (k *Keeper) SetRemainderAmount(
+ ctx sdk.Context,
+ amount sdkmath.Int,
+) {
+ // Prevent storing zero amounts. In practice, the remainder amount should
+ // only be non-zero during transactions as mint and burns should net zero
+ // due to only being used for EVM transfers.
+ if amount.IsZero() {
+ k.DeleteRemainderAmount(ctx)
+ return
+ }
+
+ // Ensure the remainder is valid before setting it. Follows the same
+ // validation as FractionalBalance with the same value range.
+ if err := types.ValidateFractionalAmount(amount); err != nil {
+ panic(fmt.Errorf("remainder amount is invalid: %w", err))
+ }
+
+ store := ctx.KVStore(k.storeKey)
+
+ amountBytes, err := amount.Marshal()
+ if err != nil {
+ panic(fmt.Errorf("failed to marshal remainder amount: %w", err))
+ }
+
+ store.Set(types.RemainderBalanceKey, amountBytes)
+}
+
+// DeleteRemainderAmount deletes the internal remainder amount.
+func (k *Keeper) DeleteRemainderAmount(
+ ctx sdk.Context,
+) {
+ store := ctx.KVStore(k.storeKey)
+ store.Delete(types.RemainderBalanceKey)
+}
diff --git a/x/precisebank/keeper/remainder_amount_test.go b/x/precisebank/keeper/remainder_amount_test.go
new file mode 100644
index 00000000..ca2969f6
--- /dev/null
+++ b/x/precisebank/keeper/remainder_amount_test.go
@@ -0,0 +1,71 @@
+package keeper_test
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGetSetRemainderAmount(t *testing.T) {
+ tk := NewMockedTestData(t)
+ ctx, k, storeKey := tk.ctx, tk.keeper, tk.storeKey
+
+ // Set amount
+ k.SetRemainderAmount(ctx, sdkmath.NewInt(100))
+
+ amt := k.GetRemainderAmount(ctx)
+ require.Equal(t, sdkmath.NewInt(100), amt)
+
+ // Set zero balance
+ k.SetRemainderAmount(ctx, sdkmath.ZeroInt())
+
+ amt = k.GetRemainderAmount(ctx)
+ require.Equal(t, sdkmath.ZeroInt(), amt)
+
+ // Get directly from store to make sure it was actually deleted
+ store := ctx.KVStore(storeKey)
+ bz := store.Get(types.RemainderBalanceKey)
+ require.Nil(t, bz)
+}
+
+func TestInvalidRemainderAmount(t *testing.T) {
+ tk := NewMockedTestData(t)
+ ctx, k := tk.ctx, tk.keeper
+
+ // Set negative amount
+ require.PanicsWithError(t, "remainder amount is invalid: non-positive amount -1", func() {
+ k.SetRemainderAmount(ctx, sdkmath.NewInt(-1))
+ })
+
+ // Set amount over max
+ require.PanicsWithError(t, "remainder amount is invalid: amount 1000000000000 exceeds max of 999999999999", func() {
+ k.SetRemainderAmount(ctx, types.ConversionFactor())
+ })
+}
+
+func TestDeleteRemainderAmount(t *testing.T) {
+ tk := NewMockedTestData(t)
+ ctx, k, storeKey := tk.ctx, tk.keeper, tk.storeKey
+
+ require.NotPanics(t, func() {
+ k.DeleteRemainderAmount(ctx)
+ })
+
+ // Set amount
+ k.SetRemainderAmount(ctx, sdkmath.NewInt(100))
+
+ amt := k.GetRemainderAmount(ctx)
+ require.Equal(t, sdkmath.NewInt(100), amt)
+
+ // Delete amount
+ k.DeleteRemainderAmount(ctx)
+
+ amt = k.GetRemainderAmount(ctx)
+ require.Equal(t, sdkmath.ZeroInt(), amt)
+
+ store := ctx.KVStore(storeKey)
+ bz := store.Get(types.RemainderBalanceKey)
+ require.Nil(t, bz)
+}
diff --git a/x/precisebank/keeper/send.go b/x/precisebank/keeper/send.go
new file mode 100644
index 00000000..ce01cc61
--- /dev/null
+++ b/x/precisebank/keeper/send.go
@@ -0,0 +1,363 @@
+package keeper
+
+import (
+ "errors"
+ "fmt"
+
+ errorsmod "cosmossdk.io/errors"
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
+)
+
+// IsSendEnabledCoins uses the parent x/bank keeper to check the coins provided
+// and returns an ErrSendDisabled if any of the coins are not configured for
+// sending. Returns nil if sending is enabled for all provided coin.
+// Note: This method is not used directly by x/evm, but is still required as
+// part of authtypes.BankKeeper. x/evm uses auth methods that require this
+// interface.
+func (k Keeper) IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error {
+ // Simply pass through to x/bank
+ return k.bk.IsSendEnabledCoins(ctx, coins...)
+}
+
+// SendCoins transfers amt coins from a sending account to a receiving account.
+// An error is returned upon failure. This handles transfers including
+// ExtendedCoinDenom and supports non-ExtendedCoinDenom transfers by passing
+// through to x/bank.
+func (k Keeper) SendCoins(
+ ctx sdk.Context,
+ from, to sdk.AccAddress,
+ amt sdk.Coins,
+) error {
+ // IsSendEnabledCoins() is only used in x/bank in msg server, not in keeper,
+ // so we should also not use it here to align with x/bank behavior.
+
+ if !amt.IsValid() {
+ return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
+ }
+
+ passthroughCoins := amt
+ extendedCoinAmount := amt.AmountOf(types.ExtendedCoinDenom)
+
+ // Remove the extended coin amount from the passthrough coins
+ if extendedCoinAmount.IsPositive() {
+ subCoin := sdk.NewCoin(types.ExtendedCoinDenom, extendedCoinAmount)
+ passthroughCoins = amt.Sub(subCoin)
+ }
+
+ // Send the passthrough coins through x/bank
+ if passthroughCoins.IsAllPositive() {
+ if err := k.bk.SendCoins(ctx, from, to, passthroughCoins); err != nil {
+ return err
+ }
+ }
+
+ // Send the extended coin amount through x/precisebank
+ if extendedCoinAmount.IsPositive() {
+ if err := k.sendExtendedCoins(ctx, from, to, extendedCoinAmount); err != nil {
+ return err
+ }
+ }
+
+ // Get a full extended coin amount (passthrough integer + fractional) ONLY
+ // for event attributes.
+ fullEmissionCoins := sdk.NewCoins(types.SumExtendedCoin(amt))
+
+ // If no passthrough integer nor fractional coins, then no event emission.
+ // We also want to emit the event with the whole equivalent extended coin
+ // if only integer coins are sent.
+ if fullEmissionCoins.IsZero() {
+ return nil
+ }
+
+ // Emit transfer event of extended denom for the FULL equivalent value.
+ ctx.EventManager().EmitEvents(sdk.Events{
+ sdk.NewEvent(
+ banktypes.EventTypeTransfer,
+ sdk.NewAttribute(banktypes.AttributeKeyRecipient, to.String()),
+ sdk.NewAttribute(banktypes.AttributeKeySender, from.String()),
+ sdk.NewAttribute(sdk.AttributeKeyAmount, fullEmissionCoins.String()),
+ ),
+ banktypes.NewCoinSpentEvent(from, fullEmissionCoins),
+ banktypes.NewCoinReceivedEvent(to, fullEmissionCoins),
+ })
+
+ return nil
+}
+
+// sendExtendedCoins transfers amt extended coins from a sending account to a
+// receiving account. An error is returned upon failure. This function is
+// called by SendCoins() and should not be called directly.
+//
+// This method covers 4 cases between two accounts - sender and receiver.
+// Depending on the fractional balance and the amount being transferred:
+// Sender account:
+// 1. Arithmetic borrow 1 integer equivalent amount of fractional coins if the
+// fractional balance is insufficient.
+// 2. No borrow if fractional balance is sufficient.
+//
+// Receiver account:
+// 1. Arithmetic carry 1 integer equivalent amount of fractional coins if the
+// received amount exceeds max fractional balance.
+// 2. No carry if received amount does not exceed max fractional balance.
+//
+// The 4 cases are:
+// 1. Sender borrow, receiver carry
+// 2. Sender borrow, NO receiver carry
+// 3. NO sender borrow, receiver carry
+// 4. NO sender borrow, NO receiver carry
+//
+// Truth table:
+// | Sender Borrow | Receiver Carry | Direct Transfer |
+// | --------------|----------------|-----------------|
+// | T | T | T |
+// | T | F | F |
+// | F | T | F |
+// | F | F | F |
+func (k Keeper) sendExtendedCoins(
+ ctx sdk.Context,
+ from, to sdk.AccAddress,
+ amt sdkmath.Int,
+) error {
+ // Sufficient balance check is done by bankkeeper.SendCoins(), for both
+ // integer and fractional-only sends. E.g. If fractional balance is
+ // insufficient, it will still incur a integer borrow which will fail if the
+ // sender does not have sufficient integer balance.
+
+ // Load required state: Account old balances
+ senderFracBal := k.GetFractionalBalance(ctx, from)
+ recipientFracBal := k.GetFractionalBalance(ctx, to)
+
+ // -------------------------------------------------------------------------
+ // Pure stateless calculations
+ integerAmt := amt.Quo(types.ConversionFactor())
+ fractionalAmt := amt.Mod(types.ConversionFactor())
+
+ // Account new fractional balances
+ senderNewFracBal, senderNeedsBorrow := subFromFractionalBalance(senderFracBal, fractionalAmt)
+ recipientNewFracBal, recipientNeedsCarry := addToFractionalBalance(recipientFracBal, fractionalAmt)
+
+ // Case #1: Sender borrow, recipient carry
+ if senderNeedsBorrow && recipientNeedsCarry {
+ // Can directly transfer borrow/carry - increase the direct transfer by 1
+ integerAmt = integerAmt.AddRaw(1)
+ }
+
+ // -------------------------------------------------------------------------
+ // Stateful operations for transfers
+
+ // This includes ALL transfers of >= conversionFactor AND Case #1
+ // Full integer amount transfer, including direct transfer of borrow/carry
+ // if any.
+ if integerAmt.IsPositive() {
+ transferCoin := sdk.NewCoin(types.IntegerCoinDenom, integerAmt)
+ if err := k.bk.SendCoins(ctx, from, to, sdk.NewCoins(transferCoin)); err != nil {
+ return k.updateInsufficientFundsError(ctx, from, amt, err)
+ }
+ }
+
+ // Case #2: Sender borrow, NO recipient carry
+ // Sender borrows by transferring 1 integer amount to reserve to account for
+ // lack of fractional balance.
+ if senderNeedsBorrow && !recipientNeedsCarry {
+ borrowCoin := sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1))
+ if err := k.bk.SendCoinsFromAccountToModule(
+ ctx,
+ from, // sender borrowing
+ types.ModuleName,
+ sdk.NewCoins(borrowCoin),
+ ); err != nil {
+ return k.updateInsufficientFundsError(ctx, from, amt, err)
+ }
+ }
+
+ // Case #3: NO sender borrow, recipient carry.
+ // Recipient's fractional balance carries over to integer balance by 1.
+ // Always send carry from reserve before receiving borrow from sender to
+ // ensure reserve always has sufficient balance starting from 0.
+ if !senderNeedsBorrow && recipientNeedsCarry {
+ reserveAddr := k.ak.GetModuleAddress(types.ModuleName)
+
+ // We use SendCoins instead of SendCoinsFromModuleToAccount to avoid
+ // the blocked addrs check. Blocked accounts should not be checked in
+ // a SendCoins operation. Only SendCoinsFromModuleToAccount should check
+ // blocked addrs which is done by the parent SendCoinsFromModuleToAccount
+ // method.
+ carryCoin := sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1))
+ if err := k.bk.SendCoins(
+ ctx,
+ reserveAddr,
+ to, // recipient carrying
+ sdk.NewCoins(carryCoin),
+ ); err != nil {
+ // Panic instead of returning error, as this will only error
+ // with invalid state or logic. Reserve should always have
+ // sufficient balance to carry fractional coins.
+ panic(fmt.Errorf("failed to carry fractional coins to %s: %w", to, err))
+ }
+ }
+
+ // Case #4: NO sender borrow, NO recipient carry
+ // No additional operations required, as the transfer of fractional coins
+ // does not incur any integer borrow or carry. New fractional balances
+ // already calculated and just need to be set.
+
+ // Persist new fractional balances to store.
+ k.SetFractionalBalance(ctx, from, senderNewFracBal)
+ k.SetFractionalBalance(ctx, to, recipientNewFracBal)
+
+ return nil
+}
+
+// subFromFractionalBalance subtracts a fractional amount from the provided
+// current fractional balance, returning the new fractional balance and true if
+// an integer borrow is required.
+func subFromFractionalBalance(
+ currentFractionalBalance sdkmath.Int,
+ amountToSub sdkmath.Int,
+) (sdkmath.Int, bool) {
+ // Enforce that currentFractionalBalance is not a full balance.
+ if currentFractionalBalance.GTE(types.ConversionFactor()) {
+ panic("currentFractionalBalance must be less than ConversionFactor")
+ }
+
+ if amountToSub.GTE(types.ConversionFactor()) {
+ panic("amountToSub must be less than ConversionFactor")
+ }
+
+ newFractionalBalance := currentFractionalBalance.Sub(amountToSub)
+
+ // Insufficient fractional balance, so we need to borrow.
+ borrowRequired := newFractionalBalance.IsNegative()
+
+ if borrowRequired {
+ // Borrowing 1 integer equivalent amount of fractional coins. We need to
+ // add 1 integer equivalent amount to the fractional balance otherwise
+ // the new fractional balance will be negative.
+ newFractionalBalance = newFractionalBalance.Add(types.ConversionFactor())
+ }
+
+ return newFractionalBalance, borrowRequired
+}
+
+// addToFractionalBalance adds a fractional amount to the provided current
+// fractional balance, returning the new fractional balance and true if a carry
+// is required.
+func addToFractionalBalance(
+ currentFractionalBalance sdkmath.Int,
+ amountToAdd sdkmath.Int,
+) (sdkmath.Int, bool) {
+ // Enforce that currentFractionalBalance is not a full balance.
+ if currentFractionalBalance.GTE(types.ConversionFactor()) {
+ panic("currentFractionalBalance must be less than ConversionFactor")
+ }
+
+ if amountToAdd.GTE(types.ConversionFactor()) {
+ panic("amountToAdd must be less than ConversionFactor")
+ }
+
+ newFractionalBalance := currentFractionalBalance.Add(amountToAdd)
+
+ // New balance exceeds max fractional balance, so we need to carry it over
+ // to the integer balance.
+ carryRequired := newFractionalBalance.GTE(types.ConversionFactor())
+
+ if carryRequired {
+ // Carry over to integer amount
+ newFractionalBalance = newFractionalBalance.Sub(types.ConversionFactor())
+ }
+
+ return newFractionalBalance, carryRequired
+}
+
+// SendCoinsFromModuleToModule transfers coins from a ModuleAccount to another.
+// It will panic if either module account does not exist. An error is returned
+// if the recipient module is the x/precisebank module account or if sending the
+// tokens fails.
+func (k Keeper) SendCoinsFromAccountToModule(
+ ctx sdk.Context,
+ senderAddr sdk.AccAddress,
+ recipientModule string,
+ amt sdk.Coins,
+) error {
+ recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule)
+ if recipientAcc == nil {
+ panic(errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule))
+ }
+
+ if recipientModule == types.ModuleName {
+ return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "module account %s is not allowed to receive funds", types.ModuleName)
+ }
+
+ return k.SendCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt)
+}
+
+// SendCoinsFromModuleToAccount transfers coins from a ModuleAccount to an AccAddress.
+// It will panic if the module account does not exist. An error is returned if
+// the recipient address is blocked, if the sender is the x/precisebank module
+// account, or if sending the tokens fails.
+func (k Keeper) SendCoinsFromModuleToAccount(
+ ctx sdk.Context,
+ senderModule string,
+ recipientAddr sdk.AccAddress,
+ amt sdk.Coins,
+) error {
+ // Identical panics to x/bank
+ senderAddr := k.ak.GetModuleAddress(senderModule)
+ if senderAddr == nil {
+ panic(errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule))
+ }
+
+ // Custom error to prevent external modules from modifying x/precisebank
+ // balances. x/precisebank module account balance is for internal reserve
+ // use only.
+ if senderModule == types.ModuleName {
+ return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "module account %s is not allowed to send funds", types.ModuleName)
+ }
+
+ // Uses x/bank BlockedAddr, no need to modify. x/precisebank should be
+ // blocked.
+ if k.bk.BlockedAddr(recipientAddr) {
+ return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", recipientAddr)
+ }
+
+ return k.SendCoins(ctx, senderAddr, recipientAddr, amt)
+}
+
+// updateInsufficientFundsError returns a modified ErrInsufficientFunds with
+// extended coin amounts if the error is due to insufficient funds. Otherwise,
+// it returns the original error. This is used since x/bank transfers will
+// return errors with integer coins, but we want the more accurate error that
+// contains the full extended coin balance and send amounts.
+func (k Keeper) updateInsufficientFundsError(
+ ctx sdk.Context,
+ addr sdk.AccAddress,
+ amt sdkmath.Int,
+ err error,
+) error {
+ if !errors.Is(err, sdkerrors.ErrInsufficientFunds) {
+ return err
+ }
+
+ // Check balance is sufficient
+ bal := k.GetBalance(ctx, addr, types.ExtendedCoinDenom)
+ coin := sdk.NewCoin(types.ExtendedCoinDenom, amt)
+
+ // TODO: This checks spendable coins and returns error with spendable
+ // coins, not full balance. If GetBalance() is modified to return the
+ // full, including locked, balance then this should be updated to deduct
+ // locked coins.
+
+ // Use sdk.NewCoins() so that it removes empty balances - ie. prints
+ // empty string if balance is 0. This is to match x/bank behavior.
+ spendable := sdk.NewCoins(bal)
+
+ return errorsmod.Wrapf(
+ sdkerrors.ErrInsufficientFunds,
+ "spendable balance %s is smaller than %s",
+ spendable, coin,
+ )
+}
diff --git a/x/precisebank/keeper/send_integration_test.go b/x/precisebank/keeper/send_integration_test.go
new file mode 100644
index 00000000..9707ec49
--- /dev/null
+++ b/x/precisebank/keeper/send_integration_test.go
@@ -0,0 +1,781 @@
+package keeper_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/0glabs/0g-chain/app"
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/testutil"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
+ minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
+ "github.com/stretchr/testify/suite"
+)
+
+type sendIntegrationTestSuite struct {
+ testutil.Suite
+}
+
+func (suite *sendIntegrationTestSuite) SetupTest() {
+ suite.Suite.SetupTest()
+}
+
+func TestSendIntegrationTestSuite(t *testing.T) {
+ suite.Run(t, new(sendIntegrationTestSuite))
+}
+
+func (suite *sendIntegrationTestSuite) TestSendCoinsFromAccountToModule_MatchingErrors() {
+ // No specific errors for SendCoinsFromAccountToModule, only 1 panic if
+ // the module account does not exist
+
+ tests := []struct {
+ name string
+ sender sdk.AccAddress
+ recipientModule string
+ sendAmount sdk.Coins
+ wantPanic string
+ }{
+ // SendCoinsFromAccountToModule specific errors/panics
+ {
+ "missing module account - passthrough",
+ sdk.AccAddress([]byte{2}),
+ "cat",
+ cs(c("usdc", 1000)),
+ "module account cat does not exist: unknown address",
+ },
+ {
+ "missing module account - extended",
+ sdk.AccAddress([]byte{2}),
+ "cat",
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ "module account cat does not exist: unknown address",
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ // Reset
+ suite.SetupTest()
+
+ suite.Require().NotEmpty(tt.wantPanic, "test case must have a wantPanic")
+
+ suite.Require().PanicsWithError(tt.wantPanic, func() {
+ suite.BankKeeper.SendCoinsFromAccountToModule(suite.Ctx, tt.sender, tt.recipientModule, tt.sendAmount)
+ }, "wantPanic should match x/bank SendCoinsFromAccountToModule panic")
+
+ suite.Require().PanicsWithError(tt.wantPanic, func() {
+ suite.Keeper.SendCoinsFromAccountToModule(suite.Ctx, tt.sender, tt.recipientModule, tt.sendAmount)
+ }, "x/precisebank panic should match x/bank SendCoinsFromAccountToModule panic")
+ })
+ }
+}
+
+func (suite *sendIntegrationTestSuite) TestSendCoinsFromModuleToAccount_MatchingErrors() {
+ // Ensure errors match x/bank errors AND panics. This needs to be well
+ // tested before SendCoins as all send tests rely on this to initialize
+ // account balances.
+ // No unit test with mock x/bank for SendCoinsFromModuleToAccount since
+ // we only are testing the errors/panics specific to the method and
+ // remaining logic is the same as SendCoins.
+
+ blockedMacAddrs := suite.App.GetBlockedMaccAddrs()
+ precisebankAddr := suite.AccountKeeper.GetModuleAddress(types.ModuleName)
+
+ var blockedAddr sdk.AccAddress
+ // Get the first blocked address
+ for addr, isBlocked := range blockedMacAddrs {
+ // Skip x/precisebank module account
+ if addr == precisebankAddr.String() {
+ continue
+ }
+
+ if isBlocked {
+ blockedAddr = sdk.MustAccAddressFromBech32(addr)
+ break
+ }
+ }
+
+ // We need a ModuleName of another module account to send funds from.
+ // x/precisebank is blocked from use with SendCoinsFromModuleToAccount as we
+ // don't want external modules to modify x/precisebank balances.
+ var senderModuleName string
+ macPerms := app.GetMaccPerms()
+ for moduleName := range macPerms {
+ if moduleName != types.ModuleName {
+ senderModuleName = moduleName
+ }
+ }
+
+ suite.Require().NotEmpty(blockedAddr, "no blocked addresses found")
+ suite.Require().NotEmpty(senderModuleName, "no sender module name found")
+
+ tests := []struct {
+ name string
+ senderModule string
+ recipient sdk.AccAddress
+ sendAmount sdk.Coins
+ wantErr string
+ wantPanic string
+ }{
+ // SendCoinsFromModuleToAccount specific errors/panics
+ {
+ "missing module account - passthrough",
+ "cat",
+ sdk.AccAddress([]byte{2}),
+ cs(c("usdc", 1000)),
+ "",
+ "module account cat does not exist: unknown address",
+ },
+ {
+ "missing module account - extended",
+ "cat",
+ sdk.AccAddress([]byte{2}),
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ "",
+ "module account cat does not exist: unknown address",
+ },
+ {
+ "blocked recipient address - passthrough",
+ senderModuleName,
+ blockedAddr,
+ cs(c("usdc", 1000)),
+ fmt.Sprintf("%s is not allowed to receive funds: unauthorized", blockedAddr.String()),
+ "",
+ },
+ {
+ "blocked recipient address - extended",
+ senderModuleName,
+ blockedAddr,
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ fmt.Sprintf("%s is not allowed to receive funds: unauthorized", blockedAddr.String()),
+ "",
+ },
+ // SendCoins specific errors/panics
+ {
+ "invalid coins",
+ senderModuleName,
+ sdk.AccAddress([]byte{2}),
+ sdk.Coins{sdk.Coin{Denom: "ukava", Amount: sdk.NewInt(-1)}},
+ "-1ukava: invalid coins",
+ "",
+ },
+ {
+ "insufficient balance - passthrough",
+ senderModuleName,
+ sdk.AccAddress([]byte{2}),
+ cs(c(types.IntegerCoinDenom, 1000)),
+ "spendable balance is smaller than 1000ukava: insufficient funds",
+ "",
+ },
+ {
+ "insufficient balance - extended",
+ senderModuleName,
+ sdk.AccAddress([]byte{2}),
+ // We can still test insufficient bal errors with "akava" since
+ // we also expect it to not exist in x/bank
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ "spendable balance is smaller than 1000akava: insufficient funds",
+ "",
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ // Reset
+ suite.SetupTest()
+
+ if tt.wantPanic == "" && tt.wantErr == "" {
+ suite.FailNow("test case must have a wantErr or wantPanic")
+ }
+
+ if tt.wantPanic != "" {
+ suite.Require().Empty(tt.wantErr, "test case must not have a wantErr if wantPanic is set")
+
+ suite.Require().PanicsWithError(tt.wantPanic, func() {
+ suite.BankKeeper.SendCoinsFromModuleToAccount(suite.Ctx, tt.senderModule, tt.recipient, tt.sendAmount)
+ }, "wantPanic should match x/bank SendCoinsFromModuleToAccount panic")
+
+ suite.Require().PanicsWithError(tt.wantPanic, func() {
+ suite.Keeper.SendCoinsFromModuleToAccount(suite.Ctx, tt.senderModule, tt.recipient, tt.sendAmount)
+ }, "x/precisebank panic should match x/bank SendCoinsFromModuleToAccount panic")
+ }
+
+ if tt.wantErr != "" {
+ bankErr := suite.BankKeeper.SendCoinsFromModuleToAccount(suite.Ctx, tt.senderModule, tt.recipient, tt.sendAmount)
+ suite.Require().Error(bankErr)
+ suite.Require().EqualError(bankErr, tt.wantErr, "expected error should match x/bank SendCoins error")
+
+ pbankErr := suite.Keeper.SendCoinsFromModuleToAccount(suite.Ctx, tt.senderModule, tt.recipient, tt.sendAmount)
+ suite.Require().Error(pbankErr)
+ // Compare strings instead of errors, as error stack is still different
+ suite.Require().Equal(
+ bankErr.Error(),
+ pbankErr.Error(),
+ "x/precisebank error should match x/bank SendCoins error",
+ )
+ }
+ })
+ }
+}
+
+func (suite *sendIntegrationTestSuite) TestSendCoins_MatchingErrors() {
+ // Ensure errors match x/bank errors
+
+ tests := []struct {
+ name string
+ initialAmount sdk.Coins
+ sendAmount sdk.Coins
+ wantErr string
+ }{
+ {
+ "invalid coins",
+ cs(),
+ sdk.Coins{sdk.Coin{Denom: "ukava", Amount: sdk.NewInt(-1)}},
+ "-1ukava: invalid coins",
+ },
+ {
+ "insufficient empty balance - passthrough",
+ cs(),
+ cs(c(types.IntegerCoinDenom, 1000)),
+ "spendable balance is smaller than 1000ukava: insufficient funds",
+ },
+ {
+ "insufficient empty balance - extended",
+ cs(),
+ // We can still test insufficient bal errors with "akava" since
+ // we also expect it to not exist in x/bank
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ "spendable balance is smaller than 1000akava: insufficient funds",
+ },
+ {
+ "insufficient non-empty balance - passthrough",
+ cs(c(types.IntegerCoinDenom, 100), c("usdc", 1000)),
+ cs(c(types.IntegerCoinDenom, 1000)),
+ "spendable balance 100ukava is smaller than 1000ukava: insufficient funds",
+ },
+ // non-empty akava transfer error is tested in SendCoins, not here since
+ // x/bank doesn't hold akava
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ // Reset
+ suite.SetupTest()
+ sender := sdk.AccAddress([]byte{1})
+ recipient := sdk.AccAddress([]byte{2})
+
+ suite.Require().NotEmpty(tt.wantErr, "test case must have a wantErr")
+
+ suite.MintToAccount(sender, tt.initialAmount)
+
+ bankErr := suite.BankKeeper.SendCoins(suite.Ctx, sender, recipient, tt.sendAmount)
+ suite.Require().Error(bankErr)
+ suite.Require().EqualError(bankErr, tt.wantErr, "expected error should match x/bank SendCoins error")
+
+ pbankErr := suite.Keeper.SendCoins(suite.Ctx, sender, recipient, tt.sendAmount)
+ suite.Require().Error(pbankErr)
+ // Compare strings instead of errors, as error stack is still different
+ suite.Require().Equal(
+ bankErr.Error(),
+ pbankErr.Error(),
+ "x/precisebank error should match x/bank SendCoins error",
+ )
+ })
+ }
+}
+
+func (suite *sendIntegrationTestSuite) TestSendCoins() {
+ // SendCoins is tested mostly in this integration test, as a unit test with
+ // mocked BankKeeper overcomplicates expected keepers and makes initializing
+ // balances very complex.
+
+ tests := []struct {
+ name string
+ giveStartBalSender sdk.Coins
+ giveStartBalRecipient sdk.Coins
+ giveAmt sdk.Coins
+ wantErr string
+ }{
+ {
+ "insufficient balance error denom matches",
+ cs(c(types.ExtendedCoinDenom, 10), c("usdc", 1000)),
+ cs(),
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ "spendable balance 10akava is smaller than 1000akava: insufficient funds",
+ },
+ {
+ "passthrough - unrelated",
+ cs(c("cats", 1000)),
+ cs(),
+ cs(c("cats", 1000)),
+ "",
+ },
+ {
+ "passthrough - integer denom",
+ cs(c(types.IntegerCoinDenom, 1000)),
+ cs(),
+ cs(c(types.IntegerCoinDenom, 1000)),
+ "",
+ },
+ {
+ "passthrough & extended",
+ cs(c(types.IntegerCoinDenom, 1000)),
+ cs(),
+ cs(c(types.IntegerCoinDenom, 10), c(types.ExtendedCoinDenom, 1)),
+ "",
+ },
+ {
+ "akava send - 1akava to 0 balance",
+ // Starting balances
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(5))),
+ cs(),
+ // Send amount
+ cs(c(types.ExtendedCoinDenom, 1)), // akava
+ "",
+ },
+ {
+ "sender borrow from integer",
+ // 1ukava, 0 fractional
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor())),
+ cs(),
+ // Send 1 with 0 fractional balance
+ cs(c(types.ExtendedCoinDenom, 1)),
+ "",
+ },
+ {
+ "sender borrow from integer - max fractional amount",
+ // 1ukava, 0 fractional
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor())),
+ cs(),
+ // Max fractional amount
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(1))),
+ "",
+ },
+ {
+ "receiver carry",
+ cs(c(types.ExtendedCoinDenom, 1000)),
+ // max fractional amount, carries over to integer
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(1))),
+ cs(c(types.ExtendedCoinDenom, 1)),
+ "",
+ },
+ {
+ "receiver carry - max fractional amount",
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(5))),
+ // max fractional amount, carries over to integer
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(1))),
+ cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(1))),
+ "",
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ suite.SetupTest()
+
+ sender := sdk.AccAddress([]byte{1})
+ recipient := sdk.AccAddress([]byte{2})
+
+ // Initialize balances
+ suite.MintToAccount(sender, tt.giveStartBalSender)
+ suite.MintToAccount(recipient, tt.giveStartBalRecipient)
+
+ senderBalBefore := suite.GetAllBalances(sender)
+ recipientBalBefore := suite.GetAllBalances(recipient)
+
+ err := suite.Keeper.SendCoins(suite.Ctx, sender, recipient, tt.giveAmt)
+ if tt.wantErr != "" {
+ suite.Require().Error(err)
+ suite.Require().EqualError(err, tt.wantErr)
+ return
+ }
+
+ suite.Require().NoError(err)
+
+ // Check balances
+ senderBalAfter := suite.GetAllBalances(sender)
+ recipientBalAfter := suite.GetAllBalances(recipient)
+
+ // Convert send amount coins to extended coins. i.e. if send coins
+ // includes ukava, convert it so that its the equivalent akava
+ // amount so its easier to compare. Compare extended coins only.
+ sendAmountFullExtended := tt.giveAmt
+ sendAmountInteger := tt.giveAmt.AmountOf(types.IntegerCoinDenom)
+ if !sendAmountInteger.IsZero() {
+ integerCoin := sdk.NewCoin(types.IntegerCoinDenom, sendAmountInteger)
+ sendAmountFullExtended = sendAmountFullExtended.Sub(integerCoin)
+
+ // Add equivalent extended coin
+ extendedCoinAmount := sendAmountInteger.Mul(types.ConversionFactor())
+ extendedCoin := sdk.NewCoin(types.ExtendedCoinDenom, extendedCoinAmount)
+ sendAmountFullExtended = sendAmountFullExtended.Add(extendedCoin)
+ }
+
+ suite.Require().Equal(
+ senderBalBefore.Sub(sendAmountFullExtended...),
+ senderBalAfter,
+ )
+
+ suite.Require().Equal(
+ recipientBalBefore.Add(sendAmountFullExtended...),
+ recipientBalAfter,
+ )
+
+ invariantFn := keeper.AllInvariants(suite.Keeper)
+ res, stop := invariantFn(suite.Ctx)
+ suite.Require().False(stop, "invariants should not stop")
+ suite.Require().Empty(res, "invariants should not return any messages")
+
+ // Check events
+
+ // FULL akava equivalent, including ukava only/mixed sends
+ sendExtendedAmount := sdk.NewCoin(
+ types.ExtendedCoinDenom,
+ sendAmountFullExtended.AmountOf(types.ExtendedCoinDenom),
+ )
+ extCoins := sdk.NewCoins(sendExtendedAmount)
+
+ // No extra events if not sending akava
+ if sendExtendedAmount.IsZero() {
+ return
+ }
+
+ extendedEvent := sdk.NewEvent(
+ banktypes.EventTypeTransfer,
+ sdk.NewAttribute(banktypes.AttributeKeyRecipient, recipient.String()),
+ sdk.NewAttribute(banktypes.AttributeKeySender, sender.String()),
+ sdk.NewAttribute(sdk.AttributeKeyAmount, sendExtendedAmount.String()),
+ )
+
+ expReceivedEvent := banktypes.NewCoinReceivedEvent(
+ recipient,
+ extCoins,
+ )
+
+ expSentEvent := banktypes.NewCoinSpentEvent(
+ sender,
+ extCoins,
+ )
+
+ events := suite.Ctx.EventManager().Events()
+
+ suite.Require().Contains(events, extendedEvent)
+ suite.Require().Contains(events, expReceivedEvent)
+ suite.Require().Contains(events, expSentEvent)
+ })
+ }
+}
+
+func (suite *sendIntegrationTestSuite) TestSendCoins_Matrix() {
+ // SendCoins is tested mostly in this integration test, as a unit test with
+ // mocked BankKeeper overcomplicates expected keepers and makes initializing
+ // balances very complex.
+
+ type startBalance struct {
+ name string
+ bal sdk.Coins
+ }
+
+ // Run through each combination of start sender/recipient balance & send amt
+ // Test matrix fields:
+ startBalances := []startBalance{
+ {"empty", cs()},
+ {"integer only", cs(c(types.IntegerCoinDenom, 1000))},
+ {"extended only", cs(c(types.ExtendedCoinDenom, 1000))},
+ {"integer & extended", cs(c(types.IntegerCoinDenom, 1000), c(types.ExtendedCoinDenom, 1000))},
+ {"integer & extended - max fractional", cs(c(types.IntegerCoinDenom, 1000), ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(1)))},
+ {"integer & extended - min fractional", cs(c(types.IntegerCoinDenom, 1000), c(types.ExtendedCoinDenom, 1))},
+ }
+
+ sendAmts := []struct {
+ name string
+ amt sdk.Coins
+ }{
+ {
+ "empty",
+ cs(),
+ },
+ {
+ "integer only",
+ cs(c(types.IntegerCoinDenom, 10)),
+ },
+ {
+ "extended only",
+ cs(c(types.ExtendedCoinDenom, 10)),
+ },
+ {
+ "integer & extended",
+ cs(c(types.IntegerCoinDenom, 10), c(types.ExtendedCoinDenom, 1000)),
+ },
+ {
+ "integer & extended - max fractional",
+ cs(c(types.IntegerCoinDenom, 10), ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(1))),
+ },
+ {
+ "integer & extended - min fractional",
+ cs(c(types.IntegerCoinDenom, 10), c(types.ExtendedCoinDenom, 1)),
+ },
+ }
+
+ for _, senderStartBal := range startBalances {
+ for _, recipientStartBal := range startBalances {
+ for _, sendAmt := range sendAmts {
+ testName := fmt.Sprintf(
+ "%s -> %s (%s -> %s), send %s (%s)",
+ senderStartBal.name, senderStartBal.bal,
+ recipientStartBal.name, recipientStartBal.bal,
+ sendAmt.name, sendAmt.amt,
+ )
+
+ suite.Run(testName, func() {
+ suite.SetupTest()
+
+ sender := sdk.AccAddress([]byte{1})
+ recipient := sdk.AccAddress([]byte{2})
+
+ // Initialize balances
+ suite.MintToAccount(sender, senderStartBal.bal)
+ suite.MintToAccount(recipient, recipientStartBal.bal)
+
+ // balances & send amount will only contain total equivalent
+ // extended coins and no integer coins so its easier to compare
+ senderBalBefore := suite.GetAllBalances(sender)
+ recipientBalBefore := suite.GetAllBalances(recipient)
+
+ sendAmtNormalized := testutil.ConvertCoinsToExtendedCoinDenom(sendAmt.amt)
+
+ err := suite.Keeper.SendCoins(suite.Ctx, sender, recipient, sendAmt.amt)
+
+ hasSufficientBal := senderBalBefore.IsAllGTE(sendAmtNormalized)
+
+ if hasSufficientBal {
+ suite.Require().NoError(err)
+ } else {
+ suite.Require().Error(err, "expected insufficient funds error")
+ // No balance checks if insufficient funds
+ return
+ }
+
+ // Check balances
+ senderBalAfter := suite.GetAllBalances(sender)
+ recipientBalAfter := suite.GetAllBalances(recipient)
+
+ // Convert send amount coins to extended coins. i.e. if send coins
+ // includes ukava, convert it so that its the equivalent akava
+ // amount so its easier to compare. Compare extended coins only.
+
+ suite.Require().Equal(
+ senderBalBefore.Sub(sendAmtNormalized...),
+ senderBalAfter,
+ )
+
+ suite.Require().Equal(
+ recipientBalBefore.Add(sendAmtNormalized...),
+ recipientBalAfter,
+ )
+
+ invariantFn := keeper.AllInvariants(suite.Keeper)
+ res, stop := invariantFn(suite.Ctx)
+ suite.Require().False(stop, "invariants should not stop")
+ suite.Require().Empty(res, "invariants should not return any messages")
+ })
+ }
+ }
+ }
+}
+
+func (suite *sendIntegrationTestSuite) TestSendCoinsFromAccountToModule() {
+ // Ensure recipient correctly matches the specified module account. Specific
+ // send amount and cases are handled by SendCoins() tests, so we are only
+ // checking SendCoinsFromAccountToModule specific behavior here.
+
+ sender := sdk.AccAddress([]byte{1})
+ recipientModule := minttypes.ModuleName
+ recipientAddr := suite.AccountKeeper.GetModuleAddress(recipientModule)
+
+ sendAmt := cs(c(types.ExtendedCoinDenom, 1000))
+
+ suite.MintToAccount(sender, sendAmt)
+
+ err := suite.Keeper.SendCoinsFromAccountToModule(
+ suite.Ctx,
+ sender,
+ recipientModule,
+ sendAmt,
+ )
+ suite.Require().NoError(err)
+
+ // Check balances
+ senderBalAfter := suite.GetAllBalances(sender)
+ recipientBalAfter := suite.GetAllBalances(recipientAddr)
+
+ suite.Require().Equal(
+ cs(),
+ senderBalAfter,
+ )
+
+ suite.Require().Equal(
+ sendAmt,
+ recipientBalAfter,
+ )
+}
+
+func (suite *sendIntegrationTestSuite) TestSendCoinsFromAccountToModule_BlockedRecipientCarry() {
+ // Carrying to module account balance. This tests that SendCoinsFromAccountToModule
+ // does not fail when sending to a blocked module account.
+
+ sender := sdk.AccAddress([]byte{1})
+
+ sendAmt := cs(c(types.ExtendedCoinDenom, 1000))
+ sendAmt2 := cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(10)))
+
+ suite.MintToAccount(sender, sendAmt.Add(sendAmt2...))
+
+ err := suite.Keeper.SendCoinsFromAccountToModule(
+ suite.Ctx,
+ sender,
+ authtypes.FeeCollectorName,
+ sendAmt,
+ )
+ suite.Require().NoError(err)
+
+ // Trigger carry for fee_collector module account
+ err = suite.Keeper.SendCoinsFromAccountToModule(
+ suite.Ctx,
+ sender,
+ authtypes.FeeCollectorName,
+ sendAmt2,
+ )
+ suite.Require().NoError(err)
+}
+
+func (suite *sendIntegrationTestSuite) TestSendCoins_BlockedRecipientCarry() {
+ // Same test as TestSendCoinsFromModuleToAccount_Blocked, but with SendCoins
+ // which also should not fail when sending to a blocked module account.
+ sender := sdk.AccAddress([]byte{1})
+
+ sendAmt := cs(c(types.ExtendedCoinDenom, 1000))
+ sendAmt2 := cs(ci(types.ExtendedCoinDenom, types.ConversionFactor().SubRaw(10)))
+
+ suite.MintToAccount(sender, sendAmt.Add(sendAmt2...))
+
+ recipient := suite.App.GetAccountKeeper().GetModuleAddress(authtypes.FeeCollectorName)
+
+ err := suite.Keeper.SendCoins(
+ suite.Ctx,
+ sender,
+ recipient,
+ sendAmt,
+ )
+ suite.Require().NoError(err)
+
+ // Trigger carry for fee_collector module account
+ err = suite.Keeper.SendCoins(
+ suite.Ctx,
+ sender,
+ recipient,
+ sendAmt2,
+ )
+ suite.Require().NoError(err)
+}
+
+func (suite *sendIntegrationTestSuite) TestSendCoinsFromModuleToAccount() {
+ // Ensure sender correctly matches the specified module account. Opposite
+ // of SendCoinsFromAccountToModule, so we are only checking the correct
+ // addresses are being used.
+
+ senderModule := "community"
+ senderAddr := suite.AccountKeeper.GetModuleAddress(senderModule)
+
+ recipient := sdk.AccAddress([]byte{1})
+
+ sendAmt := cs(c(types.ExtendedCoinDenom, 1000))
+
+ suite.MintToAccount(senderAddr, sendAmt)
+
+ err := suite.Keeper.SendCoinsFromModuleToAccount(
+ suite.Ctx,
+ senderModule,
+ recipient,
+ sendAmt,
+ )
+ suite.Require().NoError(err)
+
+ // Check balances
+ senderBalAfter := suite.GetAllBalances(senderAddr)
+ recipientBalAfter := suite.GetAllBalances(recipient)
+
+ suite.Require().Equal(
+ cs(),
+ senderBalAfter,
+ )
+
+ suite.Require().Equal(
+ sendAmt,
+ recipientBalAfter,
+ )
+}
+
+func FuzzSendCoins(f *testing.F) {
+ f.Add(uint64(100), uint64(0), uint64(2))
+ f.Add(uint64(100), uint64(100), uint64(5))
+ f.Add(types.ConversionFactor().Uint64(), uint64(0), uint64(500))
+ f.Add(
+ types.ConversionFactor().MulRaw(2).AddRaw(123948723).Uint64(),
+ types.ConversionFactor().MulRaw(2).Uint64(),
+ types.ConversionFactor().Uint64(),
+ )
+
+ f.Fuzz(func(
+ t *testing.T,
+ startBalSender uint64,
+ startBalReceiver uint64,
+ sendAmount uint64,
+ ) {
+ // Manually setup test suite since no direct Fuzz support in test suites
+ suite := new(sendIntegrationTestSuite)
+ suite.SetT(t)
+ suite.SetS(suite)
+ suite.SetupTest()
+
+ sender := sdk.AccAddress([]byte{1})
+ recipient := sdk.AccAddress([]byte{2})
+
+ // Initial balances
+ suite.MintToAccount(sender, cs(c(types.ExtendedCoinDenom, int64(startBalSender))))
+ suite.MintToAccount(recipient, cs(c(types.ExtendedCoinDenom, int64(startBalReceiver))))
+
+ // Send amount
+ sendCoins := cs(c(types.ExtendedCoinDenom, int64(sendAmount)))
+ err := suite.Keeper.SendCoins(suite.Ctx, sender, recipient, sendCoins)
+ if startBalSender < sendAmount {
+ suite.Require().Error(err, "expected insufficient funds error")
+ return
+ }
+
+ suite.Require().NoError(err)
+
+ // Check FULL balances
+ balSender := suite.GetAllBalances(sender)
+ balReceiver := suite.GetAllBalances(recipient)
+
+ suite.Require().Equal(
+ startBalSender-sendAmount,
+ balSender.AmountOf(types.ExtendedCoinDenom).Uint64(),
+ )
+ suite.Require().Equal(
+ startBalReceiver+sendAmount,
+ balReceiver.AmountOf(types.ExtendedCoinDenom).Uint64(),
+ )
+
+ // Run Invariants to ensure remainder is backing all minted fractions
+ // and in a valid state
+ allInvariantsFn := keeper.AllInvariants(suite.Keeper)
+ res, stop := allInvariantsFn(suite.Ctx)
+ suite.Require().False(stop, "invariant should not be broken")
+ suite.Require().Empty(res, "unexpected invariant message: %s", res)
+ })
+}
diff --git a/x/precisebank/keeper/send_test.go b/x/precisebank/keeper/send_test.go
new file mode 100644
index 00000000..bbcfc5a3
--- /dev/null
+++ b/x/precisebank/keeper/send_test.go
@@ -0,0 +1,47 @@
+package keeper_test
+
+import (
+ "testing"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSendCoinsFromAccountToModule_BlockedReserve(t *testing.T) {
+ // Other modules shouldn't be able to send x/precisebank coins as the module
+ // account balance is for internal reserve use only.
+
+ td := NewMockedTestData(t)
+ td.ak.EXPECT().
+ GetModuleAccount(td.ctx, types.ModuleName).
+ Return(authtypes.NewModuleAccount(
+ authtypes.NewBaseAccountWithAddress(sdk.AccAddress{100}),
+ types.ModuleName,
+ )).
+ Once()
+
+ fromAddr := sdk.AccAddress([]byte{1})
+ err := td.keeper.SendCoinsFromAccountToModule(td.ctx, fromAddr, types.ModuleName, cs(c("busd", 1000)))
+
+ require.Error(t, err)
+ require.EqualError(t, err, "module account precisebank is not allowed to receive funds: unauthorized")
+}
+
+func TestSendCoinsFromModuleToAccount_BlockedReserve(t *testing.T) {
+ // Other modules shouldn't be able to send x/precisebank module account
+ // funds.
+
+ td := NewMockedTestData(t)
+ td.ak.EXPECT().
+ GetModuleAddress(types.ModuleName).
+ Return(sdk.AccAddress{100}).
+ Once()
+
+ toAddr := sdk.AccAddress([]byte{1})
+ err := td.keeper.SendCoinsFromModuleToAccount(td.ctx, types.ModuleName, toAddr, cs(c("busd", 1000)))
+
+ require.Error(t, err)
+ require.EqualError(t, err, "module account precisebank is not allowed to send funds: unauthorized")
+}
diff --git a/x/precisebank/keeper/view.go b/x/precisebank/keeper/view.go
new file mode 100644
index 00000000..2073495b
--- /dev/null
+++ b/x/precisebank/keeper/view.go
@@ -0,0 +1,75 @@
+package keeper
+
+import (
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+// GetBalance returns the balance of a specific denom for an address. This will
+// return the extended balance for the ExtendedCoinDenom, and the regular
+// balance for all other denoms.
+func (k Keeper) GetBalance(
+ ctx sdk.Context,
+ addr sdk.AccAddress,
+ denom string,
+) sdk.Coin {
+ // Module balance should display as empty for extended denom. Module
+ // balances are **only** for the reserve which backs the fractional
+ // balances. Returning the backing balances if querying extended denom would
+ // result in a double counting of the fractional balances.
+ if denom == types.ExtendedCoinDenom && addr.Equals(k.ak.GetModuleAddress(types.ModuleName)) {
+ return sdk.NewCoin(denom, sdkmath.ZeroInt())
+ }
+
+ // Pass through to x/bank for denoms except ExtendedCoinDenom
+ if denom != types.ExtendedCoinDenom {
+ return k.bk.GetBalance(ctx, addr, denom)
+ }
+
+ // x/bank for integer balance - full balance, including locked
+ integerCoins := k.bk.GetBalance(ctx, addr, types.IntegerCoinDenom)
+
+ // x/precisebank for fractional balance
+ fractionalAmount := k.GetFractionalBalance(ctx, addr)
+
+ // (Integer * ConversionFactor) + Fractional
+ fullAmount := integerCoins.
+ Amount.
+ Mul(types.ConversionFactor()).
+ Add(fractionalAmount)
+
+ return sdk.NewCoin(types.ExtendedCoinDenom, fullAmount)
+}
+
+// SpendableCoins returns the total balances of spendable coins for an account
+// by address. If the account has no spendable coins, an empty Coins slice is
+// returned.
+func (k Keeper) SpendableCoin(
+ ctx sdk.Context,
+ addr sdk.AccAddress,
+ denom string,
+) sdk.Coin {
+ // Same as GetBalance, extended denom balances are transparent to consumers.
+ if denom == types.ExtendedCoinDenom && addr.Equals(k.ak.GetModuleAddress(types.ModuleName)) {
+ return sdk.NewCoin(denom, sdkmath.ZeroInt())
+ }
+
+ // Pass through to x/bank for denoms except ExtendedCoinDenom
+ if denom != types.ExtendedCoinDenom {
+ return k.bk.SpendableCoin(ctx, addr, denom)
+ }
+
+ // x/bank for integer balance - excluding locked
+ integerCoin := k.bk.SpendableCoin(ctx, addr, types.IntegerCoinDenom)
+
+ // x/precisebank for fractional balance
+ fractionalAmount := k.GetFractionalBalance(ctx, addr)
+
+ // Spendable = (Integer * ConversionFactor) + Fractional
+ fullAmount := integerCoin.Amount.
+ Mul(types.ConversionFactor()).
+ Add(fractionalAmount)
+
+ return sdk.NewCoin(types.ExtendedCoinDenom, fullAmount)
+}
diff --git a/x/precisebank/keeper/view_integration_test.go b/x/precisebank/keeper/view_integration_test.go
new file mode 100644
index 00000000..4241bb40
--- /dev/null
+++ b/x/precisebank/keeper/view_integration_test.go
@@ -0,0 +1,202 @@
+package keeper_test
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
+
+ "github.com/0glabs/0g-chain/x/precisebank/testutil"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ "github.com/stretchr/testify/suite"
+)
+
+type viewIntegrationTestSuite struct {
+ testutil.Suite
+}
+
+func (suite *viewIntegrationTestSuite) SetupTest() {
+ suite.Suite.SetupTest()
+}
+
+func TestViewIntegrationTestSuite(t *testing.T) {
+ suite.Run(t, new(viewIntegrationTestSuite))
+}
+
+func (suite *viewIntegrationTestSuite) TestKeeper_SpendableCoin() {
+ tests := []struct {
+ name string
+ giveDenom string // queried denom for balance
+
+ giveBankBal sdk.Coins // full balance
+ giveFractionalBal sdkmath.Int // stored fractional balance for giveAddr
+ giveLockedCoins sdk.Coins // locked coins
+
+ wantSpendableBal sdk.Coin
+ }{
+ {
+ "extended denom, no fractional - locked coins",
+ types.ExtendedCoinDenom,
+ // queried bank balance in ukava when querying for akava
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ sdkmath.ZeroInt(),
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(10))),
+ // (integer + fractional) - locked
+ sdk.NewCoin(
+ types.ExtendedCoinDenom,
+ types.ConversionFactor().MulRaw(1000-10),
+ ),
+ },
+ {
+ "extended denom, with fractional - locked coins",
+ types.ExtendedCoinDenom,
+ // queried bank balance in ukava when querying for akava
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ sdkmath.NewInt(5000),
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(10))),
+ sdk.NewCoin(
+ types.ExtendedCoinDenom,
+ // (integer - locked) + fractional
+ types.ConversionFactor().MulRaw(1000-10).AddRaw(5000),
+ ),
+ },
+ {
+ "non-extended denom - ukava returns ukava",
+ types.IntegerCoinDenom,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ sdkmath.ZeroInt(),
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(10))),
+ sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(990)),
+ },
+ {
+ "non-extended denom, with fractional - ukava returns ukava",
+ types.IntegerCoinDenom,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ // does not affect balance
+ sdkmath.NewInt(100),
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(10))),
+ sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(990)),
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ suite.SetupTest()
+
+ addr := sdk.AccAddress([]byte("test-address"))
+
+ suite.MintToAccount(addr, tt.giveBankBal)
+
+ // Set fractional balance in store before query
+ suite.Keeper.SetFractionalBalance(suite.Ctx, addr, tt.giveFractionalBal)
+
+ // Add some locked coins
+ acc := suite.AccountKeeper.GetAccount(suite.Ctx, addr)
+ if acc == nil {
+ acc = authtypes.NewBaseAccount(addr, nil, 0, 0)
+ }
+
+ vestingAcc := vestingtypes.NewPeriodicVestingAccount(
+ acc.(*authtypes.BaseAccount),
+ tt.giveLockedCoins,
+ suite.Ctx.BlockTime().Unix(),
+ vestingtypes.Periods{
+ vestingtypes.Period{
+ Length: 100,
+ Amount: tt.giveLockedCoins,
+ },
+ },
+ )
+ suite.AccountKeeper.SetAccount(suite.Ctx, vestingAcc)
+
+ fetchedLockedCoins := vestingAcc.LockedCoins(suite.Ctx.BlockTime())
+ suite.Require().Equal(
+ tt.giveLockedCoins,
+ fetchedLockedCoins,
+ "locked coins should be matching at current block time",
+ )
+
+ spendableCoinsWithLocked := suite.Keeper.SpendableCoin(suite.Ctx, addr, tt.giveDenom)
+
+ suite.Require().Equalf(
+ tt.wantSpendableBal,
+ spendableCoinsWithLocked,
+ "expected spendable coins of denom %s",
+ tt.giveDenom,
+ )
+ })
+ }
+}
+
+func (suite *viewIntegrationTestSuite) TestKeeper_HiddenReserve() {
+ // Reserve balances should not be shown to consumers of x/precisebank, as it
+ // represents the fractional balances of accounts.
+
+ moduleAddr := authtypes.NewModuleAddress(types.ModuleName)
+ addr1 := sdk.AccAddress{1}
+
+ // Make the reserve hold a non-zero balance
+ // Mint fractional coins to an account, which should cause a mint of 1
+ // integer coin to the reserve to back it.
+ extCoin := sdk.NewCoin(types.ExtendedCoinDenom, types.ConversionFactor().AddRaw(1000))
+ unrelatedCoin := sdk.NewCoin("unrelated", sdk.NewInt(1000))
+ suite.MintToAccount(
+ addr1,
+ sdk.NewCoins(
+ extCoin,
+ unrelatedCoin,
+ ),
+ )
+
+ // Check underlying x/bank balance for reserve
+ reserveIntCoin := suite.BankKeeper.GetBalance(suite.Ctx, moduleAddr, types.IntegerCoinDenom)
+ suite.Require().Equal(
+ sdkmath.NewInt(1),
+ reserveIntCoin.Amount,
+ "reserve should hold 1 integer coin",
+ )
+
+ tests := []struct {
+ name string
+ giveAddr sdk.AccAddress
+ giveDenom string
+ wantAmount sdkmath.Int
+ }{
+ {
+ "reserve account - hidden extended denom",
+ moduleAddr,
+ types.ExtendedCoinDenom,
+ sdkmath.ZeroInt(),
+ },
+ {
+ "reserve account - visible integer denom",
+ moduleAddr,
+ types.IntegerCoinDenom,
+ sdkmath.OneInt(),
+ },
+ {
+ "user account - visible extended denom",
+ addr1,
+ types.ExtendedCoinDenom,
+ extCoin.Amount,
+ },
+ {
+ "user account - visible integer denom",
+ addr1,
+ types.IntegerCoinDenom,
+ extCoin.Amount.Quo(types.ConversionFactor()),
+ },
+ }
+
+ for _, tt := range tests {
+ suite.Run(tt.name, func() {
+ coin := suite.Keeper.GetBalance(suite.Ctx, tt.giveAddr, tt.giveDenom)
+ suite.Require().Equal(tt.wantAmount.Int64(), coin.Amount.Int64())
+
+ spendableCoin := suite.Keeper.SpendableCoin(suite.Ctx, tt.giveAddr, tt.giveDenom)
+ suite.Require().Equal(tt.wantAmount.Int64(), spendableCoin.Amount.Int64())
+ })
+ }
+}
diff --git a/x/precisebank/keeper/view_test.go b/x/precisebank/keeper/view_test.go
new file mode 100644
index 00000000..01179c06
--- /dev/null
+++ b/x/precisebank/keeper/view_test.go
@@ -0,0 +1,296 @@
+package keeper_test
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestKeeper_GetBalance(t *testing.T) {
+ tests := []struct {
+ name string
+ giveDenom string // queried denom for balance
+
+ giveBankBal sdk.Coins // mocked bank balance for giveAddr
+ giveFractionalBal sdkmath.Int // stored fractional balance for giveAddr
+
+ wantBal sdk.Coin
+ }{
+ {
+ "extended denom - no fractional balance",
+ types.ExtendedCoinDenom,
+ // queried bank balance in ukava when querying for akava
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ sdkmath.ZeroInt(),
+ // integer + fractional
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_000_000_000_000)),
+ },
+ {
+ "extended denom - with fractional balance",
+ types.ExtendedCoinDenom,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ sdkmath.NewInt(100),
+ // integer + fractional
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_000_000_000_100)),
+ },
+ {
+ "extended denom - only fractional balance",
+ types.ExtendedCoinDenom,
+ // no coins in bank, only fractional balance
+ sdk.NewCoins(),
+ sdkmath.NewInt(100),
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(100)),
+ },
+ {
+ "extended denom - max fractional balance",
+ types.ExtendedCoinDenom,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ types.ConversionFactor().SubRaw(1),
+ // integer + fractional
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_999_999_999_999)),
+ },
+ {
+ "non-extended denom - ukava returns ukava",
+ types.IntegerCoinDenom,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ sdk.ZeroInt(),
+ sdk.NewCoin("ukava", sdk.NewInt(1000)),
+ },
+ {
+ "non-extended denom - unaffected by fractional balance",
+ "ukava",
+ sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000))),
+ sdkmath.NewInt(100),
+ sdk.NewCoin("ukava", sdk.NewInt(1000)),
+ },
+ {
+ "unrelated denom - no fractional",
+ "busd",
+ sdk.NewCoins(sdk.NewCoin("busd", sdk.NewInt(1000))),
+ sdk.ZeroInt(),
+ sdk.NewCoin("busd", sdk.NewInt(1000)),
+ },
+ {
+ "unrelated denom - unaffected by fractional balance",
+ "busd",
+ sdk.NewCoins(sdk.NewCoin("busd", sdk.NewInt(1000))),
+ sdkmath.NewInt(100),
+ sdk.NewCoin("busd", sdk.NewInt(1000)),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tk := NewMockedTestData(t)
+ addr := sdk.AccAddress([]byte("test-address"))
+
+ // Set fractional balance in store before query
+ tk.keeper.SetFractionalBalance(tk.ctx, addr, tt.giveFractionalBal)
+
+ // Checks address if its a reserve denom
+ if tt.giveDenom == types.ExtendedCoinDenom {
+ tk.ak.EXPECT().GetModuleAddress(types.ModuleName).
+ Return(authtypes.NewModuleAddress(types.ModuleName)).
+ Once()
+ }
+
+ if tt.giveDenom == types.ExtendedCoinDenom {
+ // No balance pass through
+ tk.bk.EXPECT().
+ GetBalance(tk.ctx, addr, types.IntegerCoinDenom).
+ RunAndReturn(func(_ sdk.Context, _ sdk.AccAddress, _ string) sdk.Coin {
+ amt := tt.giveBankBal.AmountOf(types.IntegerCoinDenom)
+ return sdk.NewCoin(types.IntegerCoinDenom, amt)
+ }).
+ Once()
+ } else {
+ // Pass through to x/bank for denoms except ExtendedCoinDenom
+ tk.bk.EXPECT().
+ GetBalance(tk.ctx, addr, tt.giveDenom).
+ RunAndReturn(func(ctx sdk.Context, aa sdk.AccAddress, s string) sdk.Coin {
+ require.Equal(t, s, tt.giveDenom, "unexpected denom passed to x/bank.GetBalance")
+
+ return sdk.NewCoin(tt.giveDenom, tt.giveBankBal.AmountOf(s))
+ }).
+ Once()
+ }
+
+ bal := tk.keeper.GetBalance(tk.ctx, addr, tt.giveDenom)
+ require.Equal(t, tt.wantBal, bal)
+ })
+ }
+}
+
+func TestKeeper_SpendableCoin(t *testing.T) {
+ tests := []struct {
+ name string
+ giveDenom string // queried denom for balance
+
+ giveBankBal sdk.Coins // mocked bank balance for giveAddr
+ giveFractionalBal sdkmath.Int // stored fractional balance for giveAddr
+
+ wantBal sdk.Coin
+ }{
+ {
+ "extended denom - no fractional balance",
+ types.ExtendedCoinDenom,
+ // queried bank balance in ukava when querying for akava
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ sdkmath.ZeroInt(),
+ // integer + fractional
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_000_000_000_000)),
+ },
+ {
+ "extended denom - with fractional balance",
+ types.ExtendedCoinDenom,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ sdkmath.NewInt(100),
+ // integer + fractional
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_000_000_000_100)),
+ },
+ {
+ "extended denom - only fractional balance",
+ types.ExtendedCoinDenom,
+ // no coins in bank, only fractional balance
+ sdk.NewCoins(),
+ sdkmath.NewInt(100),
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(100)),
+ },
+ {
+ "extended denom - max fractional balance",
+ types.ExtendedCoinDenom,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ types.ConversionFactor().SubRaw(1),
+ // integer + fractional
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_999_999_999_999)),
+ },
+ {
+ "non-extended denom - ukava returns ukava",
+ types.IntegerCoinDenom,
+ sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
+ sdk.ZeroInt(),
+ sdk.NewCoin("ukava", sdk.NewInt(1000)),
+ },
+ {
+ "non-extended denom - unaffected by fractional balance",
+ "ukava",
+ sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000))),
+ sdkmath.NewInt(100),
+ sdk.NewCoin("ukava", sdk.NewInt(1000)),
+ },
+ {
+ "unrelated denom - no fractional",
+ "busd",
+ sdk.NewCoins(sdk.NewCoin("busd", sdk.NewInt(1000))),
+ sdk.ZeroInt(),
+ sdk.NewCoin("busd", sdk.NewInt(1000)),
+ },
+ {
+ "unrelated denom - unaffected by fractional balance",
+ "busd",
+ sdk.NewCoins(sdk.NewCoin("busd", sdk.NewInt(1000))),
+ sdkmath.NewInt(100),
+ sdk.NewCoin("busd", sdk.NewInt(1000)),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tk := NewMockedTestData(t)
+ addr := sdk.AccAddress([]byte("test-address"))
+
+ // Set fractional balance in store before query
+ tk.keeper.SetFractionalBalance(tk.ctx, addr, tt.giveFractionalBal)
+
+ // If its a reserve denom, module address is checked
+ if tt.giveDenom == types.ExtendedCoinDenom {
+ tk.ak.EXPECT().GetModuleAddress(types.ModuleName).
+ Return(authtypes.NewModuleAddress(types.ModuleName)).
+ Once()
+ }
+
+ if tt.giveDenom == types.ExtendedCoinDenom {
+ // No balance pass through
+ tk.bk.EXPECT().
+ SpendableCoin(tk.ctx, addr, types.IntegerCoinDenom).
+ RunAndReturn(func(_ sdk.Context, _ sdk.AccAddress, _ string) sdk.Coin {
+ amt := tt.giveBankBal.AmountOf(types.IntegerCoinDenom)
+ return sdk.NewCoin(types.IntegerCoinDenom, amt)
+ }).
+ Once()
+ } else {
+ // Pass through to x/bank for denoms except ExtendedCoinDenom
+ tk.bk.EXPECT().
+ SpendableCoin(tk.ctx, addr, tt.giveDenom).
+ RunAndReturn(func(ctx sdk.Context, aa sdk.AccAddress, s string) sdk.Coin {
+ require.Equal(t, s, tt.giveDenom, "unexpected denom passed to x/bank.GetBalance")
+
+ return sdk.NewCoin(tt.giveDenom, tt.giveBankBal.AmountOf(s))
+ }).
+ Once()
+ }
+
+ bal := tk.keeper.SpendableCoin(tk.ctx, addr, tt.giveDenom)
+ require.Equal(t, tt.wantBal, bal)
+ })
+ }
+}
+
+func TestHiddenReserve(t *testing.T) {
+ // Reserve balances should not be shown to consumers of x/precisebank, as it
+ // represents the fractional balances of accounts.
+
+ tk := NewMockedTestData(t)
+
+ moduleAddr := authtypes.NewModuleAddress(types.ModuleName)
+
+ // No mock bankkeeper expectations, which means the zero coin is returned
+ // directly for reserve address. So the mock bankkeeper doesn't need to have
+ // a handler for getting underlying balance.
+
+ tests := []struct {
+ name string
+ denom string
+ expectedBalance sdk.Coin
+ }{
+ {"akava", types.ExtendedCoinDenom, sdk.NewCoin(types.ExtendedCoinDenom, sdkmath.ZeroInt())},
+ {"ukava", types.IntegerCoinDenom, sdk.NewCoin(types.IntegerCoinDenom, sdkmath.NewInt(1))},
+ {"unrelated denom", "cat", sdk.NewCoin("cat", sdkmath.ZeroInt())},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // 2 calls for GetBalance and SpendableCoin, only for reserve coins
+ if tt.denom == "akava" {
+ tk.ak.EXPECT().GetModuleAddress(types.ModuleName).
+ Return(moduleAddr).
+ Twice()
+ } else {
+ // Passthrough to x/bank for non-reserve denoms
+ tk.bk.EXPECT().
+ GetBalance(tk.ctx, moduleAddr, tt.denom).
+ Return(sdk.NewCoin(tt.denom, sdkmath.ZeroInt())).
+ Once()
+
+ tk.bk.EXPECT().
+ SpendableCoin(tk.ctx, moduleAddr, tt.denom).
+ Return(sdk.NewCoin(tt.denom, sdkmath.ZeroInt())).
+ Once()
+ }
+
+ // GetBalance should return zero balance for reserve address
+ coin := tk.keeper.GetBalance(tk.ctx, moduleAddr, tt.denom)
+ require.Equal(t, tt.denom, coin.Denom)
+ require.Equal(t, sdkmath.ZeroInt(), coin.Amount)
+
+ // SpendableCoin should return zero balance for reserve address
+ spendableCoin := tk.keeper.SpendableCoin(tk.ctx, moduleAddr, tt.denom)
+ require.Equal(t, tt.denom, spendableCoin.Denom)
+ require.Equal(t, sdkmath.ZeroInt(), spendableCoin.Amount)
+ })
+ }
+}
diff --git a/x/precisebank/module.go b/x/precisebank/module.go
new file mode 100644
index 00000000..f81c39c0
--- /dev/null
+++ b/x/precisebank/module.go
@@ -0,0 +1,159 @@
+package precisebank
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/grpc-ecosystem/grpc-gateway/runtime"
+ "github.com/spf13/cobra"
+
+ abci "github.com/cometbft/cometbft/abci/types"
+
+ "github.com/cosmos/cosmos-sdk/client"
+ "github.com/cosmos/cosmos-sdk/codec"
+ cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/cosmos/cosmos-sdk/types/module"
+
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+// ConsensusVersion defines the current module consensus version.
+const ConsensusVersion = 1
+
+var (
+ _ module.AppModule = AppModule{}
+ _ module.AppModuleBasic = AppModuleBasic{}
+)
+
+// ----------------------------------------------------------------------------
+// AppModuleBasic
+// ----------------------------------------------------------------------------
+
+// AppModuleBasic app module basics object
+type AppModuleBasic struct{}
+
+func NewAppModuleBasic() AppModuleBasic {
+ return AppModuleBasic{}
+}
+
+// Name get module name
+func (AppModuleBasic) Name() string {
+ return types.ModuleName
+}
+
+// Registers legacy amino codec
+func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
+ types.RegisterLegacyAminoCodec(cdc)
+}
+
+// RegisterInterfaces registers the module's interface types
+func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) {
+ types.RegisterInterfaces(reg)
+}
+
+// DefaultGenesis default genesis state
+func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage {
+ gs := types.DefaultGenesisState()
+ return cdc.MustMarshalJSON(gs)
+}
+
+// ValidateGenesis module validate genesis
+func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error {
+ var gs types.GenesisState
+ err := cdc.UnmarshalJSON(bz, &gs)
+ if err != nil {
+ return err
+ }
+ return gs.Validate()
+}
+
+// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for precisebank module.
+func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
+ if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil {
+ panic(err)
+ }
+}
+
+// GetTxCmd returns precisebank module's root tx command.
+func (a AppModuleBasic) GetTxCmd() *cobra.Command {
+ return nil
+}
+
+// GetQueryCmd returns precisebank module's root query command.
+func (AppModuleBasic) GetQueryCmd() *cobra.Command {
+ return nil
+}
+
+// ----------------------------------------------------------------------------
+// AppModule
+// ----------------------------------------------------------------------------
+
+// AppModule implements the AppModule interface for precisebank module.
+type AppModule struct {
+ AppModuleBasic
+
+ keeper keeper.Keeper
+ accountKeeper types.AccountKeeper
+ bankKeeper types.BankKeeper
+}
+
+// NewAppModule creates a new AppModule object
+func NewAppModule(
+ keeper keeper.Keeper,
+ bankKeeper types.BankKeeper,
+ accountKeeper types.AccountKeeper,
+) AppModule {
+ return AppModule{
+ AppModuleBasic: NewAppModuleBasic(),
+ keeper: keeper,
+ accountKeeper: accountKeeper,
+ bankKeeper: bankKeeper,
+ }
+}
+
+// Name returns precisebank module's name.
+func (am AppModule) Name() string {
+ return am.AppModuleBasic.Name()
+}
+
+// RegisterServices registers a GRPC query service to respond to the
+// module-specific GRPC queries.
+func (am AppModule) RegisterServices(cfg module.Configurator) {
+ types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper))
+}
+
+// RegisterInvariants registers precisebank module's invariants.
+func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
+ keeper.RegisterInvariants(ir, am.keeper, am.bankKeeper)
+}
+
+// InitGenesis performs precisebank module's genesis initialization It returns
+// no validator updates.
+func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate {
+ var genState types.GenesisState
+ // Initialize global index to index in genesis state
+ cdc.MustUnmarshalJSON(gs, &genState)
+
+ InitGenesis(ctx, am.keeper, am.accountKeeper, am.bankKeeper, &genState)
+ return []abci.ValidatorUpdate{}
+}
+
+// ExportGenesis returns precisebank module's exported genesis state as raw JSON bytes.
+func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage {
+ gs := ExportGenesis(ctx, am.keeper)
+ return cdc.MustMarshalJSON(gs)
+}
+
+// ConsensusVersion implements ConsensusVersion.
+func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion }
+
+// BeginBlock executes all ABCI BeginBlock logic respective to precisebank module.
+func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {}
+
+// EndBlock executes all ABCI EndBlock logic respective to precisebank module. It
+// returns no validator updates.
+func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
+ return []abci.ValidatorUpdate{}
+}
diff --git a/x/precisebank/testutil/fractional_balances.go b/x/precisebank/testutil/fractional_balances.go
new file mode 100644
index 00000000..7e3b4039
--- /dev/null
+++ b/x/precisebank/testutil/fractional_balances.go
@@ -0,0 +1,155 @@
+package testutil
+
+import (
+ crand "crypto/rand"
+ "math/rand"
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/cosmos/cosmos-sdk/types/address"
+ "github.com/stretchr/testify/require"
+)
+
+// randRange returns a random number in the range [min, max)
+// meaning max is never returned
+func randRange(min, max int64) int64 {
+ return rand.Int63n(max-min) + min
+}
+
+func randAccAddress() sdk.AccAddress {
+ addrBytes := make([]byte, address.MaxAddrLen)
+ _, err := crand.Read(addrBytes)
+ if err != nil {
+ panic(err)
+ }
+
+ addr := sdk.AccAddress(addrBytes)
+ if addr.Empty() {
+ panic("empty address")
+ }
+
+ return addr
+}
+
+// GenerateEqualFractionalBalances generates count number of FractionalBalances
+// with randomly generated amounts such that the sum of all amounts is a
+// multiple of types.CONVERSION_FACTOR. If a remainder is desired, any single
+// FractionalBalance can be removed from the returned slice and used as the
+// remainder.
+func GenerateEqualFractionalBalances(
+ t *testing.T,
+ count int,
+) types.FractionalBalances {
+ t.Helper()
+
+ // 1 account is not valid, as the total amount needs to be a multiple of
+ // conversionFactor. 0 < account balance < conversionFactor, so there must
+ // be at least 2
+ // NOTE: THIS IS ONLY TRUE WITH 0 REMAINDER
+ // GenerateEqualFractionalBalancesWithRemainder repurposes the last balance
+ // as the remainder, so this >= 2 requirement is not true in production code.
+ require.GreaterOrEqual(t, count, 2, "count must be at least 2 to generate balances")
+
+ fbs := make(types.FractionalBalances, count)
+ sum := sdkmath.ZeroInt()
+
+ // Random amounts for count - 1 FractionalBalances
+ for i := 0; i < count-1; i++ {
+ // Not just using sdk.AccAddress{byte(count)} since that has limited
+ // range
+ addr := randAccAddress().String()
+
+ // Random 1 < amt < ConversionFactor
+ // POSITIVE and less than ConversionFactor
+ // If it's 0, Validate() will error.
+ // Why start at 2 instead of 1? We want to make sure its divisible
+ // for the last account, more details below.
+ amt := randRange(2, types.ConversionFactor().Int64())
+ amtInt := sdkmath.NewInt(amt)
+
+ fb := types.NewFractionalBalance(addr, amtInt)
+ require.NoError(t, fb.Validate())
+
+ fbs[i] = fb
+
+ sum = sum.Add(amtInt)
+ }
+
+ // Last FractionalBalance must make sum of all balances equal to have 0
+ // fractional remainder. Effectively the amount needed to round up to the
+ // nearest integer amount to make this true.
+ // (sum + lastAmt) % CONVERSION_FACTOR = 0
+ // aka
+ // CONVERSION_FACTOR - (sum % CONVERSION_FACTOR) = lastAmt
+ addr := randAccAddress().String()
+
+ // Why do we need to Mod(conversionFactor) again?
+ // Edge case without: If sum == ConversionFactor, then lastAmt == 0 not ConversionFactor
+ // 1_000_000_000_000 - (1_000_000_000_000 % 1_000_000_000_000)
+ // = 1_000_000_000_000 - 0
+ // = 1_000_000_000_000 (invalid!)
+
+ // Note that we only have this issue in tests since we want to calculate a
+ // new valid remainder, but we only validate in the actual code.
+ amt := types.ConversionFactor().
+ Sub(sum.Mod(types.ConversionFactor())).
+ Mod(types.ConversionFactor())
+
+ // We only want to generate VALID FractionalBalances - zero would not be
+ // valid, so let's just borrow half of the previous amount. We generated
+ // amounts from 2 to ConversionFactor, so we know the previous amount is
+ // at least 2 and thus able to be split into two valid balances.
+ if amt.IsZero() {
+ fbs[count-2].Amount = fbs[count-2].Amount.QuoRaw(2)
+ amt = fbs[count-2].Amount
+ }
+
+ fb := types.NewFractionalBalance(addr, amt)
+ require.NoError(t, fb.Validate())
+
+ fbs[count-1] = fb
+
+ // Lets double check this before returning
+ verificationSum := sdkmath.ZeroInt()
+ for _, fb := range fbs {
+ verificationSum = verificationSum.Add(fb.Amount)
+ }
+ require.True(t, verificationSum.Mod(types.ConversionFactor()).IsZero())
+
+ // Also make sure no duplicate addresses
+ require.NoError(t, fbs.Validate())
+
+ return fbs
+}
+
+// GenerateEqualFractionalBalancesWithRemainder generates count number of
+// FractionalBalances with randomly generated amounts as well as a non-zero
+// remainder.
+// 0 == (sum(FractionalBalances) + remainder) % conversionFactor
+// Where remainder > 0
+func GenerateEqualFractionalBalancesWithRemainder(
+ t *testing.T,
+ count int,
+) (types.FractionalBalances, sdkmath.Int) {
+ t.Helper()
+
+ require.GreaterOrEqual(t, count, 2, "count must be at least 2 to generate both balances and remainder")
+
+ countWithRemainder := count + 1
+
+ // Generate 1 additional FractionalBalance so we can use one as remainder
+ fbs := GenerateEqualFractionalBalances(t, countWithRemainder)
+
+ // Use the last one as remainder
+ remainder := fbs[countWithRemainder-1].Amount
+
+ // Remove the balance used as remainder from the slice
+ fbs = fbs[:countWithRemainder-1]
+
+ require.Len(t, fbs, count)
+ require.NotZero(t, remainder.Int64(), "remainder must be non-zero")
+
+ return fbs, remainder
+}
diff --git a/x/precisebank/testutil/suite.go b/x/precisebank/testutil/suite.go
new file mode 100644
index 00000000..70fd35f9
--- /dev/null
+++ b/x/precisebank/testutil/suite.go
@@ -0,0 +1,151 @@
+package testutil
+
+import (
+ "time"
+
+ abci "github.com/cometbft/cometbft/abci/types"
+ "github.com/cometbft/cometbft/crypto/tmhash"
+ tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
+ tmversion "github.com/cometbft/cometbft/proto/tendermint/version"
+ tmtime "github.com/cometbft/cometbft/types/time"
+ "github.com/cometbft/cometbft/version"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
+ bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
+ minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
+ "github.com/evmos/ethermint/crypto/ethsecp256k1"
+ "github.com/stretchr/testify/suite"
+
+ "github.com/0glabs/0g-chain/app"
+ "github.com/0glabs/0g-chain/x/precisebank/keeper"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+)
+
+type Suite struct {
+ suite.Suite
+
+ App app.TestApp
+ Ctx sdk.Context
+ BankKeeper bankkeeper.Keeper
+ AccountKeeper authkeeper.AccountKeeper
+ Keeper keeper.Keeper
+}
+
+func (suite *Suite) SetupTest() {
+ tApp := app.NewTestApp()
+
+ suite.Ctx = tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
+ suite.App = tApp
+ suite.BankKeeper = tApp.GetBankKeeper()
+ suite.AccountKeeper = tApp.GetAccountKeeper()
+ suite.Keeper = tApp.GetPrecisebankKeeper()
+
+ cdc := suite.App.AppCodec()
+ coins := sdk.NewCoins(sdk.NewInt64Coin("ua0gi", 1000_000_000_000_000_000))
+ authGS := app.NewFundedGenStateWithSameCoins(cdc, coins, []sdk.AccAddress{})
+
+ gs := app.GenesisState{}
+ suite.App.InitializeFromGenesisStates(authGS, gs)
+
+ // consensus key - needed to set up evm module
+ consPriv, err := ethsecp256k1.GenerateKey()
+ suite.Require().NoError(err)
+ consAddress := sdk.ConsAddress(consPriv.PubKey().Address())
+
+ // InitializeFromGenesisStates commits first block so we start at 2 here
+ suite.Ctx = suite.App.NewContext(false, tmproto.Header{
+ Height: suite.App.LastBlockHeight() + 1,
+ ChainID: app.TestChainId,
+ Time: time.Now().UTC(),
+ ProposerAddress: consAddress.Bytes(),
+ Version: tmversion.Consensus{
+ Block: version.BlockProtocol,
+ },
+ LastBlockId: tmproto.BlockID{
+ Hash: tmhash.Sum([]byte("block_id")),
+ PartSetHeader: tmproto.PartSetHeader{
+ Total: 11,
+ Hash: tmhash.Sum([]byte("partset_header")),
+ },
+ },
+ AppHash: tmhash.Sum([]byte("app")),
+ DataHash: tmhash.Sum([]byte("data")),
+ EvidenceHash: tmhash.Sum([]byte("evidence")),
+ ValidatorsHash: tmhash.Sum([]byte("validators")),
+ NextValidatorsHash: tmhash.Sum([]byte("next_validators")),
+ ConsensusHash: tmhash.Sum([]byte("consensus")),
+ LastResultsHash: tmhash.Sum([]byte("last_result")),
+ })
+}
+
+func (suite *Suite) Commit() {
+ _ = suite.App.Commit()
+ header := suite.Ctx.BlockHeader()
+ header.Height += 1
+ suite.App.BeginBlock(abci.RequestBeginBlock{
+ Header: header,
+ })
+
+ // update ctx
+ suite.Ctx = suite.App.NewContext(false, header)
+}
+
+// MintToAccount mints coins to an account with the x/precisebank methods. This
+// must be used when minting extended coins, ie. akava coins. This depends on
+// the methods to be properly tested to be implemented correctly.
+func (suite *Suite) MintToAccount(addr sdk.AccAddress, amt sdk.Coins) {
+ accBalancesBefore := suite.GetAllBalances(addr)
+
+ err := suite.Keeper.MintCoins(suite.Ctx, minttypes.ModuleName, amt)
+ suite.Require().NoError(err)
+
+ err = suite.Keeper.SendCoinsFromModuleToAccount(suite.Ctx, minttypes.ModuleName, addr, amt)
+ suite.Require().NoError(err)
+
+ // Double check balances are correctly minted and sent to account
+ accBalancesAfter := suite.GetAllBalances(addr)
+
+ netIncrease := accBalancesAfter.Sub(accBalancesBefore...)
+ suite.Require().Equal(ConvertCoinsToExtendedCoinDenom(amt), netIncrease)
+
+ suite.T().Logf("minted %s to %s", amt, addr)
+}
+
+// GetAllBalances returns all the account balances for the given account address.
+// This returns the extended coin balance if the account has a non-zero balance,
+// WITHOUT the integer coin balance.
+func (suite *Suite) GetAllBalances(addr sdk.AccAddress) sdk.Coins {
+ // Get all balances for an account
+ bankBalances := suite.BankKeeper.GetAllBalances(suite.Ctx, addr)
+
+ // Remove integer coins from the balance
+ for _, coin := range bankBalances {
+ if coin.Denom == types.IntegerCoinDenom {
+ bankBalances = bankBalances.Sub(coin)
+ }
+ }
+
+ // Replace the integer coin with the extended coin, from x/precisebank
+ extendedBal := suite.Keeper.GetBalance(suite.Ctx, addr, types.ExtendedCoinDenom)
+
+ return bankBalances.Add(extendedBal)
+}
+
+// ConvertCoinsToExtendedCoinDenom converts sdk.Coins that includes Integer denoms
+// to sdk.Coins that includes Extended denoms of the same amount. This is useful
+// for testing to make sure only extended amounts are compared instead of double
+// counting balances.
+func ConvertCoinsToExtendedCoinDenom(coins sdk.Coins) sdk.Coins {
+ integerCoinAmt := coins.AmountOf(types.IntegerCoinDenom)
+ if integerCoinAmt.IsZero() {
+ return coins
+ }
+
+ // Remove the integer coin from the coins
+ integerCoin := sdk.NewCoin(types.IntegerCoinDenom, integerCoinAmt)
+
+ // Add the equivalent extended coin to the coins
+ extendedCoin := sdk.NewCoin(types.ExtendedCoinDenom, integerCoinAmt.Mul(types.ConversionFactor()))
+
+ return coins.Sub(integerCoin).Add(extendedCoin)
+}
diff --git a/x/precisebank/types/codec.go b/x/precisebank/types/codec.go
new file mode 100644
index 00000000..772fa40e
--- /dev/null
+++ b/x/precisebank/types/codec.go
@@ -0,0 +1,33 @@
+package types
+
+import (
+ "github.com/cosmos/cosmos-sdk/codec"
+ cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
+ cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec"
+)
+
+// RegisterLegacyAminoCodec registers the necessary evmutil interfaces and concrete types
+// on the provided LegacyAmino codec. These types are used for Amino JSON serialization.
+func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
+}
+
+func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
+ registry.RegisterImplementations((*sdk.Msg)(nil))
+}
+
+var (
+ amino = codec.NewLegacyAmino()
+ ModuleCdc = codec.NewAminoCodec(amino)
+)
+
+func init() {
+ RegisterLegacyAminoCodec(amino)
+ cryptocodec.RegisterCrypto(amino)
+ amino.Seal()
+
+ // Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be
+ // used to properly serialize MsgGrant and MsgExec instances
+ RegisterLegacyAminoCodec(authzcodec.Amino)
+}
diff --git a/x/precisebank/types/constants.go b/x/precisebank/types/constants.go
new file mode 100644
index 00000000..2f7bf7a3
--- /dev/null
+++ b/x/precisebank/types/constants.go
@@ -0,0 +1,11 @@
+package types
+
+// IntegerCoinDenom is the denomination for integer coins that are managed by
+// x/bank. This is the "true" denomination of the coin, and is also used for
+// the reserve to back all fractional coins.
+const IntegerCoinDenom = "ua0gi"
+
+// ExtendedCoinDenom is the denomination for the extended IntegerCoinDenom. This
+// not only represents the fractional balance, but the total balance of
+// integer + fractional balances.
+const ExtendedCoinDenom = "neuron"
diff --git a/x/precisebank/types/expected_keepers.go b/x/precisebank/types/expected_keepers.go
new file mode 100644
index 00000000..e80741f1
--- /dev/null
+++ b/x/precisebank/types/expected_keepers.go
@@ -0,0 +1,31 @@
+package types
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+)
+
+// AccountKeeper defines the expected account keeper interface
+type AccountKeeper interface {
+ GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI
+ GetModuleAddress(moduleName string) sdk.AccAddress
+ GetSequence(sdk.Context, sdk.AccAddress) (uint64, error)
+}
+
+// BankKeeper defines the expected bank keeper interface
+type BankKeeper interface {
+ IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error
+ GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
+ GetSupply(ctx sdk.Context, denom string) sdk.Coin
+ SpendableCoin(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
+
+ BlockedAddr(addr sdk.AccAddress) bool
+
+ SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
+ SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
+ SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
+ SendCoinsFromModuleToModule(ctx sdk.Context, senderModule string, recipientModule string, amt sdk.Coins) error
+
+ MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
+ BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
+}
diff --git a/x/precisebank/types/extended_balance.go b/x/precisebank/types/extended_balance.go
new file mode 100644
index 00000000..d4f674db
--- /dev/null
+++ b/x/precisebank/types/extended_balance.go
@@ -0,0 +1,25 @@
+package types
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+// SumExtendedCoin returns a sdk.Coin of extended coin denomination
+// with all integer and fractional amounts combined. e.g. if amount contains
+// both coins of integer denom and extended denom, this will return the total
+// amount in extended coins. This is intended to get the full value to emit in
+// events.
+func SumExtendedCoin(amt sdk.Coins) sdk.Coin {
+ // ukava converted to akava
+ integerAmount := amt.AmountOf(IntegerCoinDenom).Mul(conversionFactor)
+ // akava as is
+ extendedAmount := amt.AmountOf(ExtendedCoinDenom)
+
+ // total of ukava and akava amounts
+ fullEmissionAmount := integerAmount.Add(extendedAmount)
+
+ return sdk.NewCoin(
+ ExtendedCoinDenom,
+ fullEmissionAmount,
+ )
+}
diff --git a/x/precisebank/types/extended_balance_test.go b/x/precisebank/types/extended_balance_test.go
new file mode 100644
index 00000000..6686e5fe
--- /dev/null
+++ b/x/precisebank/types/extended_balance_test.go
@@ -0,0 +1,48 @@
+package types_test
+
+import (
+ "testing"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSumExtendedCoin(t *testing.T) {
+ tests := []struct {
+ name string
+ amt sdk.Coins
+ want sdk.Coin
+ }{
+ {
+ "empty",
+ sdk.NewCoins(),
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.ZeroInt()),
+ },
+ {
+ "only integer",
+ sdk.NewCoins(sdk.NewInt64Coin(types.IntegerCoinDenom, 100)),
+ sdk.NewCoin(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(100)),
+ },
+ {
+ "only extended",
+ sdk.NewCoins(sdk.NewInt64Coin(types.ExtendedCoinDenom, 100)),
+ sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(100)),
+ },
+ {
+ "integer and extended",
+ sdk.NewCoins(
+ sdk.NewInt64Coin(types.IntegerCoinDenom, 100),
+ sdk.NewInt64Coin(types.ExtendedCoinDenom, 100),
+ ),
+ sdk.NewCoin(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(100).AddRaw(100)),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ extVal := types.SumExtendedCoin(tt.amt)
+ require.Equal(t, tt.want, extVal)
+ })
+ }
+}
diff --git a/x/precisebank/types/fractional_balance.go b/x/precisebank/types/fractional_balance.go
new file mode 100644
index 00000000..33d02c0d
--- /dev/null
+++ b/x/precisebank/types/fractional_balance.go
@@ -0,0 +1,59 @@
+package types
+
+import (
+ fmt "fmt"
+
+ sdkmath "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+// conversionFactor is used to convert the fractional balance to integer
+// balances.
+var conversionFactor = sdkmath.NewInt(1_000_000_000_000)
+
+// ConversionFactor returns a copy of the conversionFactor used to convert the
+// fractional balance to integer balances. This is also 1 greater than the max
+// valid fractional amount (999_999_999_999):
+// 0 < FractionalBalance < conversionFactor
+func ConversionFactor() sdkmath.Int {
+ return sdkmath.NewIntFromBigIntMut(conversionFactor.BigInt())
+}
+
+// FractionalBalance returns a new FractionalBalance with the given address and
+// amount.
+func NewFractionalBalance(address string, amount sdkmath.Int) FractionalBalance {
+ return FractionalBalance{
+ Address: address,
+ Amount: amount,
+ }
+}
+
+// Validate returns an error if the FractionalBalance has an invalid address or
+// negative amount.
+func (fb FractionalBalance) Validate() error {
+ if _, err := sdk.AccAddressFromBech32(fb.Address); err != nil {
+ return err
+ }
+
+ // Validate the amount with the FractionalAmount wrapper
+ return ValidateFractionalAmount(fb.Amount)
+}
+
+// ValidateFractionalAmount checks if an sdkmath.Int is a valid fractional
+// amount, ensuring it is positive and less than or equal to the maximum
+// fractional amount.
+func ValidateFractionalAmount(amt sdkmath.Int) error {
+ if amt.IsNil() {
+ return fmt.Errorf("nil amount")
+ }
+
+ if !amt.IsPositive() {
+ return fmt.Errorf("non-positive amount %v", amt)
+ }
+
+ if amt.GTE(conversionFactor) {
+ return fmt.Errorf("amount %v exceeds max of %v", amt, conversionFactor.SubRaw(1))
+ }
+
+ return nil
+}
diff --git a/x/precisebank/types/fractional_balance_test.go b/x/precisebank/types/fractional_balance_test.go
new file mode 100644
index 00000000..015b867f
--- /dev/null
+++ b/x/precisebank/types/fractional_balance_test.go
@@ -0,0 +1,173 @@
+package types_test
+
+import (
+ "math/big"
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/app"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestConversionFactor_Immutable(t *testing.T) {
+ cf1 := types.ConversionFactor()
+ origInt64 := cf1.Int64()
+
+ // Get the internal pointer to the big.Int without copying
+ internalBigInt := cf1.BigIntMut()
+
+ // Mutate the big.Int -- .Add() mutates in place
+ internalBigInt.Add(internalBigInt, big.NewInt(5))
+ // Ensure bigInt was actually mutated
+ require.Equal(t, origInt64+5, internalBigInt.Int64())
+
+ // Fetch the max amount again
+ cf2 := types.ConversionFactor()
+
+ require.Equal(
+ t,
+ origInt64,
+ cf2.Int64(),
+ "conversion factor should be immutable",
+ )
+}
+
+func TestConversionFactor_Copied(t *testing.T) {
+ max1 := types.ConversionFactor().BigIntMut()
+ max2 := types.ConversionFactor().BigIntMut()
+
+ // Checks that the returned two pointers do not reference the same object
+ require.NotSame(t, max1, max2, "max fractional amount should be copied")
+}
+
+func TestConversionFactor(t *testing.T) {
+ require.Equal(
+ t,
+ sdkmath.NewInt(1_000_000_000_000),
+ types.ConversionFactor(),
+ "conversion factor should have 12 decimal points",
+ )
+}
+
+func TestNewFractionalBalance(t *testing.T) {
+ tests := []struct {
+ name string
+ giveAddress string
+ giveAmount sdkmath.Int
+ }{
+ {
+ "correctly sets fields",
+ "cosmos1qperwt9wrnkg5k9e5gzfgjppzpqur82k6c5a0n",
+ sdkmath.NewInt(100),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fb := types.NewFractionalBalance(tt.giveAddress, tt.giveAmount)
+
+ require.Equal(t, tt.giveAddress, fb.Address)
+ require.Equal(t, tt.giveAmount, fb.Amount)
+ })
+ }
+}
+
+func TestFractionalBalance_Validate(t *testing.T) {
+ app.SetSDKConfig()
+
+ tests := []struct {
+ name string
+ giveAddress string
+ giveAmount sdkmath.Int
+ wantErr string
+ }{
+ {
+ "valid",
+ "kava1gpxd677pp8zr97xvy3pmgk70a9vcpagsakv0tx",
+ sdkmath.NewInt(100),
+ "",
+ },
+ {
+ "valid - uppercase address",
+ "KAVA1GPXD677PP8ZR97XVY3PMGK70A9VCPAGSAKV0TX",
+ sdkmath.NewInt(100),
+ "",
+ },
+ {
+ "valid - min balance",
+ "kava1gpxd677pp8zr97xvy3pmgk70a9vcpagsakv0tx",
+ sdkmath.NewInt(1),
+ "",
+ },
+ {
+ "valid - max balance",
+ "kava1gpxd677pp8zr97xvy3pmgk70a9vcpagsakv0tx",
+ types.ConversionFactor().SubRaw(1),
+ "",
+ },
+ {
+ "invalid - 0 balance",
+ "kava1gpxd677pp8zr97xvy3pmgk70a9vcpagsakv0tx",
+ sdkmath.NewInt(0),
+ "non-positive amount 0",
+ },
+ {
+ "invalid - empty",
+ "kava1gpxd677pp8zr97xvy3pmgk70a9vcpagsakv0tx",
+ sdkmath.Int{},
+ "nil amount",
+ },
+ {
+ "invalid - mixed case address",
+ "kava1gpxd677pP8zr97xvy3pmgk70a9vcpagsakv0tx",
+ sdkmath.NewInt(100),
+ "decoding bech32 failed: string not all lowercase or all uppercase",
+ },
+ {
+ "invalid - non-bech32 address",
+ "invalid",
+ sdkmath.NewInt(100),
+ "decoding bech32 failed: invalid bech32 string length 7",
+ },
+ {
+ "invalid - wrong bech32 prefix",
+ "cosmos1qperwt9wrnkg5k9e5gzfgjppzpqur82k7gqd8n",
+ sdkmath.NewInt(100),
+ "invalid Bech32 prefix; expected kava, got cosmos",
+ },
+ {
+ "invalid - negative amount",
+ "kava1gpxd677pp8zr97xvy3pmgk70a9vcpagsakv0tx",
+ sdkmath.NewInt(-100),
+ "non-positive amount -100",
+ },
+ {
+ "invalid - max amount + 1",
+ "kava1gpxd677pp8zr97xvy3pmgk70a9vcpagsakv0tx",
+ types.ConversionFactor(),
+ "amount 1000000000000 exceeds max of 999999999999",
+ },
+ {
+ "invalid - much more than max amount",
+ "kava1gpxd677pp8zr97xvy3pmgk70a9vcpagsakv0tx",
+ sdkmath.NewInt(100000000000_000),
+ "amount 100000000000000 exceeds max of 999999999999",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fb := types.NewFractionalBalance(tt.giveAddress, tt.giveAmount)
+ err := fb.Validate()
+
+ if tt.wantErr == "" {
+ require.NoError(t, err)
+ return
+ }
+
+ require.Error(t, err)
+ require.EqualError(t, err, tt.wantErr)
+ })
+ }
+}
diff --git a/x/precisebank/types/fractional_balances.go b/x/precisebank/types/fractional_balances.go
new file mode 100644
index 00000000..656e47b3
--- /dev/null
+++ b/x/precisebank/types/fractional_balances.go
@@ -0,0 +1,48 @@
+package types
+
+import (
+ fmt "fmt"
+ "strings"
+
+ sdkmath "cosmossdk.io/math"
+)
+
+// FractionalBalances is a slice of FractionalBalance
+type FractionalBalances []FractionalBalance
+
+// Validate returns an error if any FractionalBalance in the slice is invalid.
+func (fbs FractionalBalances) Validate() error {
+ seenAddresses := make(map[string]struct{})
+
+ for _, fb := range fbs {
+ // Individual FractionalBalance validation
+ if err := fb.Validate(); err != nil {
+ return fmt.Errorf("invalid fractional balance for %s: %w", fb.Address, err)
+ }
+
+ // Make addresses all lowercase for unique check, as ALL UPPER is also
+ // a valid address.
+ lowerAddr := strings.ToLower(fb.Address)
+
+ // If this is a duplicate address, return an error
+ if _, found := seenAddresses[lowerAddr]; found {
+ return fmt.Errorf("duplicate address %v", lowerAddr)
+ }
+
+ // Mark it as seen
+ seenAddresses[lowerAddr] = struct{}{}
+ }
+
+ return nil
+}
+
+// SumAmount returns the sum of all the amounts in the slice.
+func (fbs FractionalBalances) SumAmount() sdkmath.Int {
+ sum := sdkmath.ZeroInt()
+
+ for _, fb := range fbs {
+ sum = sum.Add(fb.Amount)
+ }
+
+ return sum
+}
diff --git a/x/precisebank/types/fractional_balances_test.go b/x/precisebank/types/fractional_balances_test.go
new file mode 100644
index 00000000..9d231cb2
--- /dev/null
+++ b/x/precisebank/types/fractional_balances_test.go
@@ -0,0 +1,159 @@
+package types_test
+
+import (
+ "math/big"
+ "math/rand"
+ "strings"
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/app"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestFractionalBalances_Validate(t *testing.T) {
+ app.SetSDKConfig()
+
+ tests := []struct {
+ name string
+ fbs types.FractionalBalances
+ wantErr string
+ }{
+ {
+ "valid - empty",
+ types.FractionalBalances{},
+ "",
+ },
+ {
+ "valid - nil",
+ nil,
+ "",
+ },
+ {
+ "valid - multiple balances",
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(100)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), sdkmath.NewInt(100)),
+ types.NewFractionalBalance(sdk.AccAddress{3}.String(), sdkmath.NewInt(100)),
+ },
+ "",
+ },
+ {
+ "invalid - single invalid balance",
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(100)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), sdkmath.NewInt(-1)),
+ types.NewFractionalBalance(sdk.AccAddress{3}.String(), sdkmath.NewInt(100)),
+ },
+ "invalid fractional balance for kava1qg7c45n6: non-positive amount -1",
+ },
+ {
+ "invalid - duplicate address",
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(100)),
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(100)),
+ },
+ "duplicate address kava1qy0xn7za",
+ },
+ {
+ "invalid - duplicate address upper/lower case",
+ types.FractionalBalances{
+ types.NewFractionalBalance(
+ strings.ToLower(sdk.AccAddress{1}.String()),
+ sdkmath.NewInt(100),
+ ),
+ types.NewFractionalBalance(
+ strings.ToUpper(sdk.AccAddress{1}.String()),
+ sdkmath.NewInt(100),
+ ),
+ },
+ "duplicate address kava1qy0xn7za",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := tt.fbs.Validate()
+ if tt.wantErr == "" {
+ require.NoError(t, err)
+ return
+ }
+
+ require.Error(t, err)
+ require.EqualError(t, err, tt.wantErr)
+ })
+ }
+}
+
+func TestFractionalBalances_SumAmount(t *testing.T) {
+ generateRandomFractionalBalances := func(n int) (types.FractionalBalances, sdkmath.Int) {
+ balances := make(types.FractionalBalances, n)
+ sum := sdkmath.ZeroInt()
+
+ for i := 0; i < n; i++ {
+ addr := sdk.AccAddress{byte(i)}.String()
+ amount := sdkmath.NewInt(rand.Int63())
+ balances[i] = types.NewFractionalBalance(addr, amount)
+
+ sum = sum.Add(amount)
+ }
+
+ return balances, sum
+ }
+
+ multiBalances, sum := generateRandomFractionalBalances(10)
+
+ tests := []struct {
+ name string
+ balances types.FractionalBalances
+ wantSum sdkmath.Int
+ }{
+ {
+ "empty",
+ types.FractionalBalances{},
+ sdkmath.ZeroInt(),
+ },
+ {
+ "single",
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(100)),
+ },
+ sdkmath.NewInt(100),
+ },
+ {
+ "multiple",
+ multiBalances,
+ sum,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ sum := tt.balances.SumAmount()
+ require.Equal(t, tt.wantSum, sum)
+ })
+ }
+}
+
+func TestFractionalBalances_SumAmount_Overflow(t *testing.T) {
+ // 2^256 - 1
+ maxInt := new(big.Int).Sub(
+ new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil),
+ big.NewInt(1),
+ )
+
+ fbs := types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(100)),
+ // This is NOT valid, but just to test overflows will panic
+ types.NewFractionalBalance(
+ sdk.AccAddress{2}.String(),
+ sdkmath.NewIntFromBigInt(maxInt),
+ ),
+ }
+
+ require.PanicsWithError(t, sdkmath.ErrIntOverflow.Error(), func() {
+ _ = fbs.SumAmount()
+ })
+}
diff --git a/x/precisebank/types/genesis.go b/x/precisebank/types/genesis.go
new file mode 100644
index 00000000..38587b25
--- /dev/null
+++ b/x/precisebank/types/genesis.go
@@ -0,0 +1,70 @@
+package types
+
+import (
+ "fmt"
+
+ sdkmath "cosmossdk.io/math"
+)
+
+// NewGenesisState creates a new genesis state.
+func NewGenesisState(
+ balances FractionalBalances,
+ remainder sdkmath.Int,
+) *GenesisState {
+ return &GenesisState{
+ Balances: balances,
+ Remainder: remainder,
+ }
+}
+
+// DefaultGenesisState returns a default genesis state.
+func DefaultGenesisState() *GenesisState {
+ return NewGenesisState(FractionalBalances{}, sdkmath.ZeroInt())
+}
+
+// Validate performs basic validation of genesis data returning an error for
+// any failed validation criteria.
+func (gs *GenesisState) Validate() error {
+ // Validate all FractionalBalances
+ if err := gs.Balances.Validate(); err != nil {
+ return fmt.Errorf("invalid balances: %w", err)
+ }
+
+ if gs.Remainder.IsNil() {
+ return fmt.Errorf("nil remainder amount")
+ }
+
+ // Validate remainder, 0 <= remainder <= maxFractionalAmount
+ if gs.Remainder.IsNegative() {
+ return fmt.Errorf("negative remainder amount %s", gs.Remainder)
+ }
+
+ if gs.Remainder.GTE(conversionFactor) {
+ return fmt.Errorf("remainder %v exceeds max of %v", gs.Remainder, conversionFactor.SubRaw(1))
+ }
+
+ // Determine if sum(fractionalBalances) + remainder = whole integer value
+ // i.e total of all fractional balances + remainder == 0 fractional digits
+ sum := gs.Balances.SumAmount()
+ sumWithRemainder := sum.Add(gs.Remainder)
+
+ offBy := sumWithRemainder.Mod(conversionFactor)
+
+ if !offBy.IsZero() {
+ return fmt.Errorf(
+ "sum of fractional balances %v + remainder %v is not a multiple of %v",
+ sum,
+ gs.Remainder,
+ conversionFactor,
+ )
+ }
+
+ return nil
+}
+
+// TotalAmountWithRemainder returns the total amount of all balances in the
+// genesis state, including both fractional balances and the remainder. A bit
+// more verbose WithRemainder to ensure its clearly different from SumAmount().
+func (gs *GenesisState) TotalAmountWithRemainder() sdkmath.Int {
+ return gs.Balances.SumAmount().Add(gs.Remainder)
+}
diff --git a/x/precisebank/types/genesis.pb.go b/x/precisebank/types/genesis.pb.go
new file mode 100644
index 00000000..b8edfa55
--- /dev/null
+++ b/x/precisebank/types/genesis.pb.go
@@ -0,0 +1,607 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: zgc/precisebank/v1/genesis.proto
+
+package types
+
+import (
+ cosmossdk_io_math "cosmossdk.io/math"
+ fmt "fmt"
+ _ "github.com/cosmos/cosmos-proto"
+ _ "github.com/cosmos/gogoproto/gogoproto"
+ proto "github.com/cosmos/gogoproto/proto"
+ io "io"
+ math "math"
+ math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+// GenesisState defines the precisebank module's genesis state.
+type GenesisState struct {
+ // balances is a list of all the balances in the precisebank module.
+ Balances FractionalBalances `protobuf:"bytes,1,rep,name=balances,proto3,castrepeated=FractionalBalances" json:"balances"`
+ // remainder is an internal value of how much extra fractional digits are
+ // still backed by the reserve, but not assigned to any account.
+ Remainder cosmossdk_io_math.Int `protobuf:"bytes,2,opt,name=remainder,proto3,customtype=cosmossdk.io/math.Int" json:"remainder"`
+}
+
+func (m *GenesisState) Reset() { *m = GenesisState{} }
+func (m *GenesisState) String() string { return proto.CompactTextString(m) }
+func (*GenesisState) ProtoMessage() {}
+func (*GenesisState) Descriptor() ([]byte, []int) {
+ return fileDescriptor_4029a0f159e1fc46, []int{0}
+}
+func (m *GenesisState) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic)
+ } else {
+ b = b[:cap(b)]
+ n, err := m.MarshalToSizedBuffer(b)
+ if err != nil {
+ return nil, err
+ }
+ return b[:n], nil
+ }
+}
+func (m *GenesisState) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_GenesisState.Merge(m, src)
+}
+func (m *GenesisState) XXX_Size() int {
+ return m.Size()
+}
+func (m *GenesisState) XXX_DiscardUnknown() {
+ xxx_messageInfo_GenesisState.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GenesisState proto.InternalMessageInfo
+
+func (m *GenesisState) GetBalances() FractionalBalances {
+ if m != nil {
+ return m.Balances
+ }
+ return nil
+}
+
+// FractionalBalance defines the fractional portion of an account balance
+type FractionalBalance struct {
+ // address is the address of the balance holder.
+ Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+ // amount indicates amount of only the fractional balance owned by the
+ // address. FractionalBalance currently only supports tracking 1 single asset,
+ // e.g. fractional balances of ukava.
+ Amount cosmossdk_io_math.Int `protobuf:"bytes,2,opt,name=amount,proto3,customtype=cosmossdk.io/math.Int" json:"amount"`
+}
+
+func (m *FractionalBalance) Reset() { *m = FractionalBalance{} }
+func (m *FractionalBalance) String() string { return proto.CompactTextString(m) }
+func (*FractionalBalance) ProtoMessage() {}
+func (*FractionalBalance) Descriptor() ([]byte, []int) {
+ return fileDescriptor_4029a0f159e1fc46, []int{1}
+}
+func (m *FractionalBalance) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *FractionalBalance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_FractionalBalance.Marshal(b, m, deterministic)
+ } else {
+ b = b[:cap(b)]
+ n, err := m.MarshalToSizedBuffer(b)
+ if err != nil {
+ return nil, err
+ }
+ return b[:n], nil
+ }
+}
+func (m *FractionalBalance) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_FractionalBalance.Merge(m, src)
+}
+func (m *FractionalBalance) XXX_Size() int {
+ return m.Size()
+}
+func (m *FractionalBalance) XXX_DiscardUnknown() {
+ xxx_messageInfo_FractionalBalance.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_FractionalBalance proto.InternalMessageInfo
+
+func init() {
+ proto.RegisterType((*GenesisState)(nil), "zgc.precisebank.v1.GenesisState")
+ proto.RegisterType((*FractionalBalance)(nil), "zgc.precisebank.v1.FractionalBalance")
+}
+
+func init() { proto.RegisterFile("zgc/precisebank/v1/genesis.proto", fileDescriptor_4029a0f159e1fc46) }
+
+var fileDescriptor_4029a0f159e1fc46 = []byte{
+ // 357 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xa8, 0x4a, 0x4f, 0xd6,
+ 0x2f, 0x28, 0x4a, 0x4d, 0xce, 0x2c, 0x4e, 0x4d, 0x4a, 0xcc, 0xcb, 0xd6, 0x2f, 0x33, 0xd4, 0x4f,
+ 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0xaa, 0x4a,
+ 0x4f, 0xd6, 0x43, 0x52, 0xa1, 0x57, 0x66, 0x28, 0x25, 0x99, 0x9c, 0x5f, 0x9c, 0x9b, 0x5f, 0x1c,
+ 0x0f, 0x56, 0xa1, 0x0f, 0xe1, 0x40, 0x94, 0x4b, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x43, 0xc4, 0x41,
+ 0x2c, 0x88, 0xa8, 0xd2, 0x0e, 0x46, 0x2e, 0x1e, 0x77, 0x88, 0xb1, 0xc1, 0x25, 0x89, 0x25, 0xa9,
+ 0x42, 0xb1, 0x5c, 0x1c, 0x49, 0x89, 0x39, 0x89, 0x79, 0xc9, 0xa9, 0xc5, 0x12, 0x8c, 0x0a, 0xcc,
+ 0x1a, 0xdc, 0x46, 0xaa, 0x7a, 0x98, 0x16, 0xe9, 0xb9, 0x15, 0x25, 0x26, 0x97, 0x64, 0xe6, 0xe7,
+ 0x25, 0xe6, 0x38, 0x41, 0x54, 0x3b, 0x49, 0x9d, 0xb8, 0x27, 0xcf, 0xb0, 0xea, 0xbe, 0xbc, 0x10,
+ 0x86, 0x54, 0x71, 0x10, 0xdc, 0x48, 0x21, 0x4f, 0x2e, 0xce, 0xa2, 0xd4, 0xdc, 0xc4, 0xcc, 0xbc,
+ 0x94, 0xd4, 0x22, 0x09, 0x26, 0x05, 0x46, 0x0d, 0x4e, 0x27, 0x6d, 0x90, 0xc6, 0x5b, 0xf7, 0xe4,
+ 0x45, 0x21, 0xce, 0x2d, 0x4e, 0xc9, 0xd6, 0xcb, 0xcc, 0xd7, 0xcf, 0x4d, 0x2c, 0xc9, 0xd0, 0xf3,
+ 0xcc, 0x2b, 0xb9, 0xb4, 0x45, 0x97, 0x0b, 0xea, 0x0f, 0xcf, 0xbc, 0x92, 0x20, 0x84, 0x6e, 0xa5,
+ 0x69, 0x8c, 0x5c, 0x82, 0x18, 0x76, 0x09, 0x19, 0x71, 0xb1, 0x27, 0xa6, 0xa4, 0x14, 0xa5, 0x16,
+ 0x83, 0x9c, 0x0f, 0x32, 0x5e, 0xe2, 0xd2, 0x16, 0x5d, 0x11, 0xa8, 0x09, 0x8e, 0x10, 0x99, 0xe0,
+ 0x92, 0xa2, 0xcc, 0xbc, 0xf4, 0x20, 0x98, 0x42, 0x21, 0x67, 0x2e, 0xb6, 0xc4, 0xdc, 0xfc, 0xd2,
+ 0xbc, 0x12, 0x72, 0x5c, 0x04, 0xd5, 0x6a, 0xc5, 0xd1, 0xb1, 0x40, 0x9e, 0xe1, 0xc5, 0x02, 0x79,
+ 0x06, 0x27, 0x8f, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71,
+ 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0xd2, 0x4b, 0xcf,
+ 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x37, 0x48, 0xcf, 0x49, 0x4c, 0x2a, 0xd6,
+ 0x37, 0x48, 0xd7, 0x4d, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0xaf, 0x40, 0x89, 0xed, 0x92, 0xca, 0x82,
+ 0xd4, 0xe2, 0x24, 0x36, 0x70, 0x24, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xc3, 0xe5, 0x62,
+ 0x3b, 0x0d, 0x02, 0x00, 0x00,
+}
+
+func (m *GenesisState) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBuffer(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ {
+ size := m.Remainder.Size()
+ i -= size
+ if _, err := m.Remainder.MarshalTo(dAtA[i:]); err != nil {
+ return 0, err
+ }
+ i = encodeVarintGenesis(dAtA, i, uint64(size))
+ }
+ i--
+ dAtA[i] = 0x12
+ if len(m.Balances) > 0 {
+ for iNdEx := len(m.Balances) - 1; iNdEx >= 0; iNdEx-- {
+ {
+ size, err := m.Balances[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+ if err != nil {
+ return 0, err
+ }
+ i -= size
+ i = encodeVarintGenesis(dAtA, i, uint64(size))
+ }
+ i--
+ dAtA[i] = 0xa
+ }
+ }
+ return len(dAtA) - i, nil
+}
+
+func (m *FractionalBalance) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBuffer(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *FractionalBalance) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *FractionalBalance) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ {
+ size := m.Amount.Size()
+ i -= size
+ if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil {
+ return 0, err
+ }
+ i = encodeVarintGenesis(dAtA, i, uint64(size))
+ }
+ i--
+ dAtA[i] = 0x12
+ if len(m.Address) > 0 {
+ i -= len(m.Address)
+ copy(dAtA[i:], m.Address)
+ i = encodeVarintGenesis(dAtA, i, uint64(len(m.Address)))
+ i--
+ dAtA[i] = 0xa
+ }
+ return len(dAtA) - i, nil
+}
+
+func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int {
+ offset -= sovGenesis(v)
+ base := offset
+ for v >= 1<<7 {
+ dAtA[offset] = uint8(v&0x7f | 0x80)
+ v >>= 7
+ offset++
+ }
+ dAtA[offset] = uint8(v)
+ return base
+}
+func (m *GenesisState) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ if len(m.Balances) > 0 {
+ for _, e := range m.Balances {
+ l = e.Size()
+ n += 1 + l + sovGenesis(uint64(l))
+ }
+ }
+ l = m.Remainder.Size()
+ n += 1 + l + sovGenesis(uint64(l))
+ return n
+}
+
+func (m *FractionalBalance) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = len(m.Address)
+ if l > 0 {
+ n += 1 + l + sovGenesis(uint64(l))
+ }
+ l = m.Amount.Size()
+ n += 1 + l + sovGenesis(uint64(l))
+ return n
+}
+
+func sovGenesis(x uint64) (n int) {
+ return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozGenesis(x uint64) (n int) {
+ return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *GenesisState) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowGenesis
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: GenesisState: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Balances", wireType)
+ }
+ var msglen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowGenesis
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ msglen |= int(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if msglen < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ postIndex := iNdEx + msglen
+ if postIndex < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Balances = append(m.Balances, FractionalBalance{})
+ if err := m.Balances[len(m.Balances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Remainder", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowGenesis
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ if err := m.Remainder.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipGenesis(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *FractionalBalance) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowGenesis
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: FractionalBalance: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: FractionalBalance: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowGenesis
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Address = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowGenesis
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipGenesis(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return ErrInvalidLengthGenesis
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func skipGenesis(dAtA []byte) (n int, err error) {
+ l := len(dAtA)
+ iNdEx := 0
+ depth := 0
+ for iNdEx < l {
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowGenesis
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ wireType := int(wire & 0x7)
+ switch wireType {
+ case 0:
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowGenesis
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ iNdEx++
+ if dAtA[iNdEx-1] < 0x80 {
+ break
+ }
+ }
+ case 1:
+ iNdEx += 8
+ case 2:
+ var length int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowGenesis
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ length |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if length < 0 {
+ return 0, ErrInvalidLengthGenesis
+ }
+ iNdEx += length
+ case 3:
+ depth++
+ case 4:
+ if depth == 0 {
+ return 0, ErrUnexpectedEndOfGroupGenesis
+ }
+ depth--
+ case 5:
+ iNdEx += 4
+ default:
+ return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+ }
+ if iNdEx < 0 {
+ return 0, ErrInvalidLengthGenesis
+ }
+ if depth == 0 {
+ return iNdEx, nil
+ }
+ }
+ return 0, io.ErrUnexpectedEOF
+}
+
+var (
+ ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling")
+ ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow")
+ ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/x/precisebank/types/genesis_test.go b/x/precisebank/types/genesis_test.go
new file mode 100644
index 00000000..e51014b8
--- /dev/null
+++ b/x/precisebank/types/genesis_test.go
@@ -0,0 +1,295 @@
+package types_test
+
+import (
+ "testing"
+
+ sdkmath "cosmossdk.io/math"
+ "github.com/0glabs/0g-chain/app"
+ "github.com/0glabs/0g-chain/x/precisebank/testutil"
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGenesisStateValidate_Basic(t *testing.T) {
+ app.SetSDKConfig()
+
+ testCases := []struct {
+ name string
+ genesisState *types.GenesisState
+ wantErr string
+ }{
+ {
+ "valid - default genesisState",
+ types.DefaultGenesisState(),
+ "",
+ },
+ {
+ "valid - empty balances, zero remainder",
+ &types.GenesisState{
+ Remainder: sdkmath.ZeroInt(),
+ },
+ "",
+ },
+ {
+ "valid - nil balances",
+ types.NewGenesisState(nil, sdkmath.ZeroInt()),
+ "",
+ },
+ {
+ "valid - max remainder amount",
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ },
+ types.ConversionFactor().SubRaw(1),
+ ),
+ "",
+ },
+ {
+ "invalid - empty genesisState (nil remainder)",
+ &types.GenesisState{},
+ "nil remainder amount",
+ },
+ {
+ "valid - balances add up",
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ },
+ sdkmath.ZeroInt(),
+ ),
+ "invalid balances: duplicate address kava1qy0xn7za",
+ },
+ {
+ "invalid - calls (single) FractionalBalance.Validate()",
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), sdkmath.NewInt(-1)),
+ },
+ sdkmath.ZeroInt(),
+ ),
+ "invalid balances: invalid fractional balance for kava1qg7c45n6: non-positive amount -1",
+ },
+ {
+ "invalid - calls (slice) FractionalBalances.Validate()",
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ },
+ sdkmath.ZeroInt(),
+ ),
+ "invalid balances: duplicate address kava1qy0xn7za",
+ },
+ {
+ "invalid - negative remainder",
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), sdkmath.NewInt(1)),
+ },
+ sdkmath.NewInt(-1),
+ ),
+ "negative remainder amount -1",
+ },
+ {
+ "invalid - too large remainder",
+ types.NewGenesisState(
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), sdkmath.NewInt(1)),
+ },
+ types.ConversionFactor(),
+ ),
+ "remainder 1000000000000 exceeds max of 999999999999",
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+ t.Run(tc.name, func(tt *testing.T) {
+ err := tc.genesisState.Validate()
+
+ if tc.wantErr == "" {
+ require.NoError(tt, err)
+ } else {
+ require.Error(tt, err)
+ require.EqualError(tt, err, tc.wantErr)
+ }
+ })
+ }
+}
+
+func TestGenesisStateValidate_Total(t *testing.T) {
+ testCases := []struct {
+ name string
+ buildGenesisState func() *types.GenesisState
+ containsErr string
+ }{
+ {
+ "valid - empty balances, zero remainder",
+ func() *types.GenesisState {
+ return types.NewGenesisState(nil, sdkmath.ZeroInt())
+ },
+ "",
+ },
+ {
+ "valid - non-zero balances, zero remainder",
+ func() *types.GenesisState {
+ fbs := testutil.GenerateEqualFractionalBalances(t, 100)
+ require.Len(t, fbs, 100)
+
+ return types.NewGenesisState(fbs, sdkmath.ZeroInt())
+ },
+ "",
+ },
+ {
+ "valid - non-zero balances, non-zero remainder",
+ func() *types.GenesisState {
+ fbs, remainder := testutil.GenerateEqualFractionalBalancesWithRemainder(t, 100)
+
+ require.Len(t, fbs, 100)
+ require.NotZero(t, remainder.Int64())
+
+ t.Log("remainder:", remainder)
+
+ return types.NewGenesisState(fbs, remainder)
+ },
+ "",
+ },
+ {
+ "invalid - non-zero balances, invalid remainder",
+ func() *types.GenesisState {
+ fbs, remainder := testutil.GenerateEqualFractionalBalancesWithRemainder(t, 100)
+
+ require.Len(t, fbs, 100)
+ require.NotZero(t, remainder.Int64())
+
+ // Wrong remainder - should be non-zero
+ return types.NewGenesisState(fbs, sdkmath.ZeroInt())
+ },
+ // balances are randomly generated so we can't set the exact value in the error message
+ // "sum of fractional balances 52885778295370 ... "
+ "+ remainder 0 is not a multiple of 1000000000000",
+ },
+ {
+ "invalid - empty balances, non-zero remainder",
+ func() *types.GenesisState {
+ return types.NewGenesisState(types.FractionalBalances{}, sdkmath.NewInt(1))
+ },
+ "sum of fractional balances 0 + remainder 1 is not a multiple of 1000000000000",
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+ t.Run(tc.name, func(tt *testing.T) {
+ err := tc.buildGenesisState().Validate()
+
+ if tc.containsErr == "" {
+ require.NoError(tt, err)
+ } else {
+ require.Error(tt, err)
+ require.ErrorContains(tt, err, tc.containsErr)
+ }
+ })
+ }
+}
+
+func TestGenesisState_TotalAmountWithRemainder(t *testing.T) {
+ tests := []struct {
+ name string
+ giveBalances types.FractionalBalances
+ giveRemainder sdkmath.Int
+ wantTotalAmountWithRemainder sdkmath.Int
+ }{
+ {
+ "empty balances, zero remainder",
+ types.FractionalBalances{},
+ sdkmath.ZeroInt(),
+ sdkmath.ZeroInt(),
+ },
+ {
+ "non-empty balances, zero remainder",
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().QuoRaw(2)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().QuoRaw(2)),
+ },
+ sdkmath.ZeroInt(),
+ types.ConversionFactor(),
+ },
+ {
+ "non-empty balances, 1 remainder",
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().QuoRaw(2)),
+ types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().QuoRaw(2).SubRaw(1)),
+ },
+ sdkmath.OneInt(),
+ types.ConversionFactor(),
+ },
+ {
+ "non-empty balances, max remainder",
+ types.FractionalBalances{
+ types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.OneInt()),
+ },
+ types.ConversionFactor().SubRaw(1),
+ types.ConversionFactor(),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gs := types.NewGenesisState(
+ tt.giveBalances,
+ tt.giveRemainder,
+ )
+
+ require.NoError(t, gs.Validate(), "genesis state should be valid before testing total amount")
+
+ totalAmt := gs.TotalAmountWithRemainder()
+ require.Equal(t, tt.wantTotalAmountWithRemainder, totalAmt, "total amount should be balances + remainder")
+ })
+ }
+}
+
+func FuzzGenesisStateValidate_NonZeroRemainder(f *testing.F) {
+ f.Add(5)
+ f.Add(100)
+ f.Add(30)
+
+ f.Fuzz(func(t *testing.T, count int) {
+ // Need at least 2 so we can generate both balances and remainder
+ if count < 2 {
+ t.Skip("count < 2")
+ }
+
+ fbs, remainder := testutil.GenerateEqualFractionalBalancesWithRemainder(t, count)
+
+ t.Logf("count: %v", count)
+ t.Logf("remainder: %v", remainder)
+
+ gs := types.NewGenesisState(fbs, remainder)
+ require.NoError(t, gs.Validate())
+ })
+}
+
+func FuzzGenesisStateValidate_ZeroRemainder(f *testing.F) {
+ f.Add(5)
+ f.Add(100)
+ f.Add(30)
+
+ f.Fuzz(func(t *testing.T, count int) {
+ // Need at least 2 as 1 account with non-zero balance & no remainder is not valid
+ if count < 2 {
+ t.Skip("count < 2")
+ }
+
+ fbs := testutil.GenerateEqualFractionalBalances(t, count)
+
+ gs := types.NewGenesisState(fbs, sdkmath.ZeroInt())
+ require.NoError(t, gs.Validate())
+ })
+}
diff --git a/x/precisebank/types/keys.go b/x/precisebank/types/keys.go
new file mode 100644
index 00000000..b1b5ae1b
--- /dev/null
+++ b/x/precisebank/types/keys.go
@@ -0,0 +1,28 @@
+package types
+
+import sdk "github.com/cosmos/cosmos-sdk/types"
+
+const (
+ // ModuleName name that will be used throughout the module
+ ModuleName = "precisebank"
+
+ StoreKey = ModuleName
+
+ // RouterKey Top level router key
+ RouterKey = ModuleName
+)
+
+// key prefixes for store
+var (
+ FractionalBalancePrefix = []byte{0x01} // address -> fractional balance
+)
+
+// Keys for store that are not prefixed
+var (
+ RemainderBalanceKey = []byte{0x02} // fractional balance remainder
+)
+
+// FractionalBalanceKey returns a key from an address
+func FractionalBalanceKey(address sdk.AccAddress) []byte {
+ return address.Bytes()
+}
diff --git a/x/precisebank/types/keys_test.go b/x/precisebank/types/keys_test.go
new file mode 100644
index 00000000..ca74c0d3
--- /dev/null
+++ b/x/precisebank/types/keys_test.go
@@ -0,0 +1,17 @@
+package types_test
+
+import (
+ "testing"
+
+ "github.com/0glabs/0g-chain/x/precisebank/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestFractionalBalanceKey(t *testing.T) {
+ addr := sdk.AccAddress([]byte("test-address"))
+
+ key := types.FractionalBalanceKey(addr)
+ require.Equal(t, addr.Bytes(), key)
+ require.Equal(t, addr, sdk.AccAddress(key), "key should be able to be converted back to address")
+}
diff --git a/x/precisebank/types/mocks/MockAccountKeeper.go b/x/precisebank/types/mocks/MockAccountKeeper.go
new file mode 100644
index 00000000..982ba7cd
--- /dev/null
+++ b/x/precisebank/types/mocks/MockAccountKeeper.go
@@ -0,0 +1,191 @@
+// Code generated by mockery v2.43.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ mock "github.com/stretchr/testify/mock"
+
+ types "github.com/cosmos/cosmos-sdk/types"
+)
+
+// MockAccountKeeper is an autogenerated mock type for the AccountKeeper type
+type MockAccountKeeper struct {
+ mock.Mock
+}
+
+type MockAccountKeeper_Expecter struct {
+ mock *mock.Mock
+}
+
+func (_m *MockAccountKeeper) EXPECT() *MockAccountKeeper_Expecter {
+ return &MockAccountKeeper_Expecter{mock: &_m.Mock}
+}
+
+// GetModuleAccount provides a mock function with given fields: ctx, moduleName
+func (_m *MockAccountKeeper) GetModuleAccount(ctx types.Context, moduleName string) authtypes.ModuleAccountI {
+ ret := _m.Called(ctx, moduleName)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetModuleAccount")
+ }
+
+ var r0 authtypes.ModuleAccountI
+ if rf, ok := ret.Get(0).(func(types.Context, string) authtypes.ModuleAccountI); ok {
+ r0 = rf(ctx, moduleName)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(authtypes.ModuleAccountI)
+ }
+ }
+
+ return r0
+}
+
+// MockAccountKeeper_GetModuleAccount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetModuleAccount'
+type MockAccountKeeper_GetModuleAccount_Call struct {
+ *mock.Call
+}
+
+// GetModuleAccount is a helper method to define mock.On call
+// - ctx types.Context
+// - moduleName string
+func (_e *MockAccountKeeper_Expecter) GetModuleAccount(ctx interface{}, moduleName interface{}) *MockAccountKeeper_GetModuleAccount_Call {
+ return &MockAccountKeeper_GetModuleAccount_Call{Call: _e.mock.On("GetModuleAccount", ctx, moduleName)}
+}
+
+func (_c *MockAccountKeeper_GetModuleAccount_Call) Run(run func(ctx types.Context, moduleName string)) *MockAccountKeeper_GetModuleAccount_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(string))
+ })
+ return _c
+}
+
+func (_c *MockAccountKeeper_GetModuleAccount_Call) Return(_a0 authtypes.ModuleAccountI) *MockAccountKeeper_GetModuleAccount_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockAccountKeeper_GetModuleAccount_Call) RunAndReturn(run func(types.Context, string) authtypes.ModuleAccountI) *MockAccountKeeper_GetModuleAccount_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// GetModuleAddress provides a mock function with given fields: moduleName
+func (_m *MockAccountKeeper) GetModuleAddress(moduleName string) types.AccAddress {
+ ret := _m.Called(moduleName)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetModuleAddress")
+ }
+
+ var r0 types.AccAddress
+ if rf, ok := ret.Get(0).(func(string) types.AccAddress); ok {
+ r0 = rf(moduleName)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(types.AccAddress)
+ }
+ }
+
+ return r0
+}
+
+// MockAccountKeeper_GetModuleAddress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetModuleAddress'
+type MockAccountKeeper_GetModuleAddress_Call struct {
+ *mock.Call
+}
+
+// GetModuleAddress is a helper method to define mock.On call
+// - moduleName string
+func (_e *MockAccountKeeper_Expecter) GetModuleAddress(moduleName interface{}) *MockAccountKeeper_GetModuleAddress_Call {
+ return &MockAccountKeeper_GetModuleAddress_Call{Call: _e.mock.On("GetModuleAddress", moduleName)}
+}
+
+func (_c *MockAccountKeeper_GetModuleAddress_Call) Run(run func(moduleName string)) *MockAccountKeeper_GetModuleAddress_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(string))
+ })
+ return _c
+}
+
+func (_c *MockAccountKeeper_GetModuleAddress_Call) Return(_a0 types.AccAddress) *MockAccountKeeper_GetModuleAddress_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockAccountKeeper_GetModuleAddress_Call) RunAndReturn(run func(string) types.AccAddress) *MockAccountKeeper_GetModuleAddress_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// GetSequence provides a mock function with given fields: _a0, _a1
+func (_m *MockAccountKeeper) GetSequence(_a0 types.Context, _a1 types.AccAddress) (uint64, error) {
+ ret := _m.Called(_a0, _a1)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetSequence")
+ }
+
+ var r0 uint64
+ var r1 error
+ if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress) (uint64, error)); ok {
+ return rf(_a0, _a1)
+ }
+ if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress) uint64); ok {
+ r0 = rf(_a0, _a1)
+ } else {
+ r0 = ret.Get(0).(uint64)
+ }
+
+ if rf, ok := ret.Get(1).(func(types.Context, types.AccAddress) error); ok {
+ r1 = rf(_a0, _a1)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// MockAccountKeeper_GetSequence_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSequence'
+type MockAccountKeeper_GetSequence_Call struct {
+ *mock.Call
+}
+
+// GetSequence is a helper method to define mock.On call
+// - _a0 types.Context
+// - _a1 types.AccAddress
+func (_e *MockAccountKeeper_Expecter) GetSequence(_a0 interface{}, _a1 interface{}) *MockAccountKeeper_GetSequence_Call {
+ return &MockAccountKeeper_GetSequence_Call{Call: _e.mock.On("GetSequence", _a0, _a1)}
+}
+
+func (_c *MockAccountKeeper_GetSequence_Call) Run(run func(_a0 types.Context, _a1 types.AccAddress)) *MockAccountKeeper_GetSequence_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(types.AccAddress))
+ })
+ return _c
+}
+
+func (_c *MockAccountKeeper_GetSequence_Call) Return(_a0 uint64, _a1 error) *MockAccountKeeper_GetSequence_Call {
+ _c.Call.Return(_a0, _a1)
+ return _c
+}
+
+func (_c *MockAccountKeeper_GetSequence_Call) RunAndReturn(run func(types.Context, types.AccAddress) (uint64, error)) *MockAccountKeeper_GetSequence_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// NewMockAccountKeeper creates a new instance of MockAccountKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockAccountKeeper(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *MockAccountKeeper {
+ mock := &MockAccountKeeper{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/x/precisebank/types/mocks/MockBankKeeper.go b/x/precisebank/types/mocks/MockBankKeeper.go
new file mode 100644
index 00000000..cb823410
--- /dev/null
+++ b/x/precisebank/types/mocks/MockBankKeeper.go
@@ -0,0 +1,578 @@
+// Code generated by mockery v2.43.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ mock "github.com/stretchr/testify/mock"
+
+ types "github.com/cosmos/cosmos-sdk/types"
+)
+
+// MockBankKeeper is an autogenerated mock type for the BankKeeper type
+type MockBankKeeper struct {
+ mock.Mock
+}
+
+type MockBankKeeper_Expecter struct {
+ mock *mock.Mock
+}
+
+func (_m *MockBankKeeper) EXPECT() *MockBankKeeper_Expecter {
+ return &MockBankKeeper_Expecter{mock: &_m.Mock}
+}
+
+// BlockedAddr provides a mock function with given fields: addr
+func (_m *MockBankKeeper) BlockedAddr(addr types.AccAddress) bool {
+ ret := _m.Called(addr)
+
+ if len(ret) == 0 {
+ panic("no return value specified for BlockedAddr")
+ }
+
+ var r0 bool
+ if rf, ok := ret.Get(0).(func(types.AccAddress) bool); ok {
+ r0 = rf(addr)
+ } else {
+ r0 = ret.Get(0).(bool)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_BlockedAddr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockedAddr'
+type MockBankKeeper_BlockedAddr_Call struct {
+ *mock.Call
+}
+
+// BlockedAddr is a helper method to define mock.On call
+// - addr types.AccAddress
+func (_e *MockBankKeeper_Expecter) BlockedAddr(addr interface{}) *MockBankKeeper_BlockedAddr_Call {
+ return &MockBankKeeper_BlockedAddr_Call{Call: _e.mock.On("BlockedAddr", addr)}
+}
+
+func (_c *MockBankKeeper_BlockedAddr_Call) Run(run func(addr types.AccAddress)) *MockBankKeeper_BlockedAddr_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.AccAddress))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_BlockedAddr_Call) Return(_a0 bool) *MockBankKeeper_BlockedAddr_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_BlockedAddr_Call) RunAndReturn(run func(types.AccAddress) bool) *MockBankKeeper_BlockedAddr_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// BurnCoins provides a mock function with given fields: ctx, moduleName, amt
+func (_m *MockBankKeeper) BurnCoins(ctx types.Context, moduleName string, amt types.Coins) error {
+ ret := _m.Called(ctx, moduleName, amt)
+
+ if len(ret) == 0 {
+ panic("no return value specified for BurnCoins")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(types.Context, string, types.Coins) error); ok {
+ r0 = rf(ctx, moduleName, amt)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_BurnCoins_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BurnCoins'
+type MockBankKeeper_BurnCoins_Call struct {
+ *mock.Call
+}
+
+// BurnCoins is a helper method to define mock.On call
+// - ctx types.Context
+// - moduleName string
+// - amt types.Coins
+func (_e *MockBankKeeper_Expecter) BurnCoins(ctx interface{}, moduleName interface{}, amt interface{}) *MockBankKeeper_BurnCoins_Call {
+ return &MockBankKeeper_BurnCoins_Call{Call: _e.mock.On("BurnCoins", ctx, moduleName, amt)}
+}
+
+func (_c *MockBankKeeper_BurnCoins_Call) Run(run func(ctx types.Context, moduleName string, amt types.Coins)) *MockBankKeeper_BurnCoins_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(string), args[2].(types.Coins))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_BurnCoins_Call) Return(_a0 error) *MockBankKeeper_BurnCoins_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_BurnCoins_Call) RunAndReturn(run func(types.Context, string, types.Coins) error) *MockBankKeeper_BurnCoins_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// GetBalance provides a mock function with given fields: ctx, addr, denom
+func (_m *MockBankKeeper) GetBalance(ctx types.Context, addr types.AccAddress, denom string) types.Coin {
+ ret := _m.Called(ctx, addr, denom)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetBalance")
+ }
+
+ var r0 types.Coin
+ if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, string) types.Coin); ok {
+ r0 = rf(ctx, addr, denom)
+ } else {
+ r0 = ret.Get(0).(types.Coin)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_GetBalance_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBalance'
+type MockBankKeeper_GetBalance_Call struct {
+ *mock.Call
+}
+
+// GetBalance is a helper method to define mock.On call
+// - ctx types.Context
+// - addr types.AccAddress
+// - denom string
+func (_e *MockBankKeeper_Expecter) GetBalance(ctx interface{}, addr interface{}, denom interface{}) *MockBankKeeper_GetBalance_Call {
+ return &MockBankKeeper_GetBalance_Call{Call: _e.mock.On("GetBalance", ctx, addr, denom)}
+}
+
+func (_c *MockBankKeeper_GetBalance_Call) Run(run func(ctx types.Context, addr types.AccAddress, denom string)) *MockBankKeeper_GetBalance_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(types.AccAddress), args[2].(string))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_GetBalance_Call) Return(_a0 types.Coin) *MockBankKeeper_GetBalance_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_GetBalance_Call) RunAndReturn(run func(types.Context, types.AccAddress, string) types.Coin) *MockBankKeeper_GetBalance_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// GetSupply provides a mock function with given fields: ctx, denom
+func (_m *MockBankKeeper) GetSupply(ctx types.Context, denom string) types.Coin {
+ ret := _m.Called(ctx, denom)
+
+ if len(ret) == 0 {
+ panic("no return value specified for GetSupply")
+ }
+
+ var r0 types.Coin
+ if rf, ok := ret.Get(0).(func(types.Context, string) types.Coin); ok {
+ r0 = rf(ctx, denom)
+ } else {
+ r0 = ret.Get(0).(types.Coin)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_GetSupply_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSupply'
+type MockBankKeeper_GetSupply_Call struct {
+ *mock.Call
+}
+
+// GetSupply is a helper method to define mock.On call
+// - ctx types.Context
+// - denom string
+func (_e *MockBankKeeper_Expecter) GetSupply(ctx interface{}, denom interface{}) *MockBankKeeper_GetSupply_Call {
+ return &MockBankKeeper_GetSupply_Call{Call: _e.mock.On("GetSupply", ctx, denom)}
+}
+
+func (_c *MockBankKeeper_GetSupply_Call) Run(run func(ctx types.Context, denom string)) *MockBankKeeper_GetSupply_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(string))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_GetSupply_Call) Return(_a0 types.Coin) *MockBankKeeper_GetSupply_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_GetSupply_Call) RunAndReturn(run func(types.Context, string) types.Coin) *MockBankKeeper_GetSupply_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// IsSendEnabledCoins provides a mock function with given fields: ctx, coins
+func (_m *MockBankKeeper) IsSendEnabledCoins(ctx types.Context, coins ...types.Coin) error {
+ _va := make([]interface{}, len(coins))
+ for _i := range coins {
+ _va[_i] = coins[_i]
+ }
+ var _ca []interface{}
+ _ca = append(_ca, ctx)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ if len(ret) == 0 {
+ panic("no return value specified for IsSendEnabledCoins")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(types.Context, ...types.Coin) error); ok {
+ r0 = rf(ctx, coins...)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_IsSendEnabledCoins_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsSendEnabledCoins'
+type MockBankKeeper_IsSendEnabledCoins_Call struct {
+ *mock.Call
+}
+
+// IsSendEnabledCoins is a helper method to define mock.On call
+// - ctx types.Context
+// - coins ...types.Coin
+func (_e *MockBankKeeper_Expecter) IsSendEnabledCoins(ctx interface{}, coins ...interface{}) *MockBankKeeper_IsSendEnabledCoins_Call {
+ return &MockBankKeeper_IsSendEnabledCoins_Call{Call: _e.mock.On("IsSendEnabledCoins",
+ append([]interface{}{ctx}, coins...)...)}
+}
+
+func (_c *MockBankKeeper_IsSendEnabledCoins_Call) Run(run func(ctx types.Context, coins ...types.Coin)) *MockBankKeeper_IsSendEnabledCoins_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ variadicArgs := make([]types.Coin, len(args)-1)
+ for i, a := range args[1:] {
+ if a != nil {
+ variadicArgs[i] = a.(types.Coin)
+ }
+ }
+ run(args[0].(types.Context), variadicArgs...)
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_IsSendEnabledCoins_Call) Return(_a0 error) *MockBankKeeper_IsSendEnabledCoins_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_IsSendEnabledCoins_Call) RunAndReturn(run func(types.Context, ...types.Coin) error) *MockBankKeeper_IsSendEnabledCoins_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// MintCoins provides a mock function with given fields: ctx, moduleName, amt
+func (_m *MockBankKeeper) MintCoins(ctx types.Context, moduleName string, amt types.Coins) error {
+ ret := _m.Called(ctx, moduleName, amt)
+
+ if len(ret) == 0 {
+ panic("no return value specified for MintCoins")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(types.Context, string, types.Coins) error); ok {
+ r0 = rf(ctx, moduleName, amt)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_MintCoins_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MintCoins'
+type MockBankKeeper_MintCoins_Call struct {
+ *mock.Call
+}
+
+// MintCoins is a helper method to define mock.On call
+// - ctx types.Context
+// - moduleName string
+// - amt types.Coins
+func (_e *MockBankKeeper_Expecter) MintCoins(ctx interface{}, moduleName interface{}, amt interface{}) *MockBankKeeper_MintCoins_Call {
+ return &MockBankKeeper_MintCoins_Call{Call: _e.mock.On("MintCoins", ctx, moduleName, amt)}
+}
+
+func (_c *MockBankKeeper_MintCoins_Call) Run(run func(ctx types.Context, moduleName string, amt types.Coins)) *MockBankKeeper_MintCoins_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(string), args[2].(types.Coins))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_MintCoins_Call) Return(_a0 error) *MockBankKeeper_MintCoins_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_MintCoins_Call) RunAndReturn(run func(types.Context, string, types.Coins) error) *MockBankKeeper_MintCoins_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// SendCoins provides a mock function with given fields: ctx, fromAddr, toAddr, amt
+func (_m *MockBankKeeper) SendCoins(ctx types.Context, fromAddr types.AccAddress, toAddr types.AccAddress, amt types.Coins) error {
+ ret := _m.Called(ctx, fromAddr, toAddr, amt)
+
+ if len(ret) == 0 {
+ panic("no return value specified for SendCoins")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, types.AccAddress, types.Coins) error); ok {
+ r0 = rf(ctx, fromAddr, toAddr, amt)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_SendCoins_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendCoins'
+type MockBankKeeper_SendCoins_Call struct {
+ *mock.Call
+}
+
+// SendCoins is a helper method to define mock.On call
+// - ctx types.Context
+// - fromAddr types.AccAddress
+// - toAddr types.AccAddress
+// - amt types.Coins
+func (_e *MockBankKeeper_Expecter) SendCoins(ctx interface{}, fromAddr interface{}, toAddr interface{}, amt interface{}) *MockBankKeeper_SendCoins_Call {
+ return &MockBankKeeper_SendCoins_Call{Call: _e.mock.On("SendCoins", ctx, fromAddr, toAddr, amt)}
+}
+
+func (_c *MockBankKeeper_SendCoins_Call) Run(run func(ctx types.Context, fromAddr types.AccAddress, toAddr types.AccAddress, amt types.Coins)) *MockBankKeeper_SendCoins_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(types.AccAddress), args[2].(types.AccAddress), args[3].(types.Coins))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_SendCoins_Call) Return(_a0 error) *MockBankKeeper_SendCoins_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_SendCoins_Call) RunAndReturn(run func(types.Context, types.AccAddress, types.AccAddress, types.Coins) error) *MockBankKeeper_SendCoins_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// SendCoinsFromAccountToModule provides a mock function with given fields: ctx, senderAddr, recipientModule, amt
+func (_m *MockBankKeeper) SendCoinsFromAccountToModule(ctx types.Context, senderAddr types.AccAddress, recipientModule string, amt types.Coins) error {
+ ret := _m.Called(ctx, senderAddr, recipientModule, amt)
+
+ if len(ret) == 0 {
+ panic("no return value specified for SendCoinsFromAccountToModule")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, string, types.Coins) error); ok {
+ r0 = rf(ctx, senderAddr, recipientModule, amt)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_SendCoinsFromAccountToModule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendCoinsFromAccountToModule'
+type MockBankKeeper_SendCoinsFromAccountToModule_Call struct {
+ *mock.Call
+}
+
+// SendCoinsFromAccountToModule is a helper method to define mock.On call
+// - ctx types.Context
+// - senderAddr types.AccAddress
+// - recipientModule string
+// - amt types.Coins
+func (_e *MockBankKeeper_Expecter) SendCoinsFromAccountToModule(ctx interface{}, senderAddr interface{}, recipientModule interface{}, amt interface{}) *MockBankKeeper_SendCoinsFromAccountToModule_Call {
+ return &MockBankKeeper_SendCoinsFromAccountToModule_Call{Call: _e.mock.On("SendCoinsFromAccountToModule", ctx, senderAddr, recipientModule, amt)}
+}
+
+func (_c *MockBankKeeper_SendCoinsFromAccountToModule_Call) Run(run func(ctx types.Context, senderAddr types.AccAddress, recipientModule string, amt types.Coins)) *MockBankKeeper_SendCoinsFromAccountToModule_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(types.AccAddress), args[2].(string), args[3].(types.Coins))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_SendCoinsFromAccountToModule_Call) Return(_a0 error) *MockBankKeeper_SendCoinsFromAccountToModule_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_SendCoinsFromAccountToModule_Call) RunAndReturn(run func(types.Context, types.AccAddress, string, types.Coins) error) *MockBankKeeper_SendCoinsFromAccountToModule_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// SendCoinsFromModuleToAccount provides a mock function with given fields: ctx, senderModule, recipientAddr, amt
+func (_m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx types.Context, senderModule string, recipientAddr types.AccAddress, amt types.Coins) error {
+ ret := _m.Called(ctx, senderModule, recipientAddr, amt)
+
+ if len(ret) == 0 {
+ panic("no return value specified for SendCoinsFromModuleToAccount")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(types.Context, string, types.AccAddress, types.Coins) error); ok {
+ r0 = rf(ctx, senderModule, recipientAddr, amt)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_SendCoinsFromModuleToAccount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendCoinsFromModuleToAccount'
+type MockBankKeeper_SendCoinsFromModuleToAccount_Call struct {
+ *mock.Call
+}
+
+// SendCoinsFromModuleToAccount is a helper method to define mock.On call
+// - ctx types.Context
+// - senderModule string
+// - recipientAddr types.AccAddress
+// - amt types.Coins
+func (_e *MockBankKeeper_Expecter) SendCoinsFromModuleToAccount(ctx interface{}, senderModule interface{}, recipientAddr interface{}, amt interface{}) *MockBankKeeper_SendCoinsFromModuleToAccount_Call {
+ return &MockBankKeeper_SendCoinsFromModuleToAccount_Call{Call: _e.mock.On("SendCoinsFromModuleToAccount", ctx, senderModule, recipientAddr, amt)}
+}
+
+func (_c *MockBankKeeper_SendCoinsFromModuleToAccount_Call) Run(run func(ctx types.Context, senderModule string, recipientAddr types.AccAddress, amt types.Coins)) *MockBankKeeper_SendCoinsFromModuleToAccount_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(string), args[2].(types.AccAddress), args[3].(types.Coins))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_SendCoinsFromModuleToAccount_Call) Return(_a0 error) *MockBankKeeper_SendCoinsFromModuleToAccount_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_SendCoinsFromModuleToAccount_Call) RunAndReturn(run func(types.Context, string, types.AccAddress, types.Coins) error) *MockBankKeeper_SendCoinsFromModuleToAccount_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// SendCoinsFromModuleToModule provides a mock function with given fields: ctx, senderModule, recipientModule, amt
+func (_m *MockBankKeeper) SendCoinsFromModuleToModule(ctx types.Context, senderModule string, recipientModule string, amt types.Coins) error {
+ ret := _m.Called(ctx, senderModule, recipientModule, amt)
+
+ if len(ret) == 0 {
+ panic("no return value specified for SendCoinsFromModuleToModule")
+ }
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(types.Context, string, string, types.Coins) error); ok {
+ r0 = rf(ctx, senderModule, recipientModule, amt)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_SendCoinsFromModuleToModule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendCoinsFromModuleToModule'
+type MockBankKeeper_SendCoinsFromModuleToModule_Call struct {
+ *mock.Call
+}
+
+// SendCoinsFromModuleToModule is a helper method to define mock.On call
+// - ctx types.Context
+// - senderModule string
+// - recipientModule string
+// - amt types.Coins
+func (_e *MockBankKeeper_Expecter) SendCoinsFromModuleToModule(ctx interface{}, senderModule interface{}, recipientModule interface{}, amt interface{}) *MockBankKeeper_SendCoinsFromModuleToModule_Call {
+ return &MockBankKeeper_SendCoinsFromModuleToModule_Call{Call: _e.mock.On("SendCoinsFromModuleToModule", ctx, senderModule, recipientModule, amt)}
+}
+
+func (_c *MockBankKeeper_SendCoinsFromModuleToModule_Call) Run(run func(ctx types.Context, senderModule string, recipientModule string, amt types.Coins)) *MockBankKeeper_SendCoinsFromModuleToModule_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(string), args[2].(string), args[3].(types.Coins))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_SendCoinsFromModuleToModule_Call) Return(_a0 error) *MockBankKeeper_SendCoinsFromModuleToModule_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_SendCoinsFromModuleToModule_Call) RunAndReturn(run func(types.Context, string, string, types.Coins) error) *MockBankKeeper_SendCoinsFromModuleToModule_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// SpendableCoin provides a mock function with given fields: ctx, addr, denom
+func (_m *MockBankKeeper) SpendableCoin(ctx types.Context, addr types.AccAddress, denom string) types.Coin {
+ ret := _m.Called(ctx, addr, denom)
+
+ if len(ret) == 0 {
+ panic("no return value specified for SpendableCoin")
+ }
+
+ var r0 types.Coin
+ if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, string) types.Coin); ok {
+ r0 = rf(ctx, addr, denom)
+ } else {
+ r0 = ret.Get(0).(types.Coin)
+ }
+
+ return r0
+}
+
+// MockBankKeeper_SpendableCoin_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SpendableCoin'
+type MockBankKeeper_SpendableCoin_Call struct {
+ *mock.Call
+}
+
+// SpendableCoin is a helper method to define mock.On call
+// - ctx types.Context
+// - addr types.AccAddress
+// - denom string
+func (_e *MockBankKeeper_Expecter) SpendableCoin(ctx interface{}, addr interface{}, denom interface{}) *MockBankKeeper_SpendableCoin_Call {
+ return &MockBankKeeper_SpendableCoin_Call{Call: _e.mock.On("SpendableCoin", ctx, addr, denom)}
+}
+
+func (_c *MockBankKeeper_SpendableCoin_Call) Run(run func(ctx types.Context, addr types.AccAddress, denom string)) *MockBankKeeper_SpendableCoin_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(types.Context), args[1].(types.AccAddress), args[2].(string))
+ })
+ return _c
+}
+
+func (_c *MockBankKeeper_SpendableCoin_Call) Return(_a0 types.Coin) *MockBankKeeper_SpendableCoin_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *MockBankKeeper_SpendableCoin_Call) RunAndReturn(run func(types.Context, types.AccAddress, string) types.Coin) *MockBankKeeper_SpendableCoin_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// NewMockBankKeeper creates a new instance of MockBankKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockBankKeeper(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *MockBankKeeper {
+ mock := &MockBankKeeper{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/x/precisebank/types/query.pb.go b/x/precisebank/types/query.pb.go
new file mode 100644
index 00000000..33bfdaf0
--- /dev/null
+++ b/x/precisebank/types/query.pb.go
@@ -0,0 +1,1245 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: zgc/precisebank/v1/query.proto
+
+package types
+
+import (
+ context "context"
+ fmt "fmt"
+ types "github.com/cosmos/cosmos-sdk/types"
+ _ "github.com/cosmos/gogoproto/gogoproto"
+ grpc1 "github.com/cosmos/gogoproto/grpc"
+ proto "github.com/cosmos/gogoproto/proto"
+ _ "google.golang.org/genproto/googleapis/api/annotations"
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+ io "io"
+ math "math"
+ math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+// QueryTotalFractionalBalancesRequest defines the request type for Query/TotalFractionalBalances method.
+type QueryTotalFractionalBalancesRequest struct {
+}
+
+func (m *QueryTotalFractionalBalancesRequest) Reset() { *m = QueryTotalFractionalBalancesRequest{} }
+func (m *QueryTotalFractionalBalancesRequest) String() string { return proto.CompactTextString(m) }
+func (*QueryTotalFractionalBalancesRequest) ProtoMessage() {}
+func (*QueryTotalFractionalBalancesRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c77e9b73389f3988, []int{0}
+}
+func (m *QueryTotalFractionalBalancesRequest) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *QueryTotalFractionalBalancesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_QueryTotalFractionalBalancesRequest.Marshal(b, m, deterministic)
+ } else {
+ b = b[:cap(b)]
+ n, err := m.MarshalToSizedBuffer(b)
+ if err != nil {
+ return nil, err
+ }
+ return b[:n], nil
+ }
+}
+func (m *QueryTotalFractionalBalancesRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_QueryTotalFractionalBalancesRequest.Merge(m, src)
+}
+func (m *QueryTotalFractionalBalancesRequest) XXX_Size() int {
+ return m.Size()
+}
+func (m *QueryTotalFractionalBalancesRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_QueryTotalFractionalBalancesRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryTotalFractionalBalancesRequest proto.InternalMessageInfo
+
+// QueryTotalFractionalBalancesResponse defines the response type for Query/TotalFractionalBalances method.
+type QueryTotalFractionalBalancesResponse struct {
+ // total is the total sum of all fractional balances managed by the precisebank
+ // module.
+ Total types.Coin `protobuf:"bytes,1,opt,name=total,proto3" json:"total"`
+}
+
+func (m *QueryTotalFractionalBalancesResponse) Reset() { *m = QueryTotalFractionalBalancesResponse{} }
+func (m *QueryTotalFractionalBalancesResponse) String() string { return proto.CompactTextString(m) }
+func (*QueryTotalFractionalBalancesResponse) ProtoMessage() {}
+func (*QueryTotalFractionalBalancesResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c77e9b73389f3988, []int{1}
+}
+func (m *QueryTotalFractionalBalancesResponse) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *QueryTotalFractionalBalancesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_QueryTotalFractionalBalancesResponse.Marshal(b, m, deterministic)
+ } else {
+ b = b[:cap(b)]
+ n, err := m.MarshalToSizedBuffer(b)
+ if err != nil {
+ return nil, err
+ }
+ return b[:n], nil
+ }
+}
+func (m *QueryTotalFractionalBalancesResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_QueryTotalFractionalBalancesResponse.Merge(m, src)
+}
+func (m *QueryTotalFractionalBalancesResponse) XXX_Size() int {
+ return m.Size()
+}
+func (m *QueryTotalFractionalBalancesResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_QueryTotalFractionalBalancesResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryTotalFractionalBalancesResponse proto.InternalMessageInfo
+
+// QueryRemainderRequest defines the request type for Query/Remainder method.
+type QueryRemainderRequest struct {
+}
+
+func (m *QueryRemainderRequest) Reset() { *m = QueryRemainderRequest{} }
+func (m *QueryRemainderRequest) String() string { return proto.CompactTextString(m) }
+func (*QueryRemainderRequest) ProtoMessage() {}
+func (*QueryRemainderRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c77e9b73389f3988, []int{2}
+}
+func (m *QueryRemainderRequest) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *QueryRemainderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_QueryRemainderRequest.Marshal(b, m, deterministic)
+ } else {
+ b = b[:cap(b)]
+ n, err := m.MarshalToSizedBuffer(b)
+ if err != nil {
+ return nil, err
+ }
+ return b[:n], nil
+ }
+}
+func (m *QueryRemainderRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_QueryRemainderRequest.Merge(m, src)
+}
+func (m *QueryRemainderRequest) XXX_Size() int {
+ return m.Size()
+}
+func (m *QueryRemainderRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_QueryRemainderRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryRemainderRequest proto.InternalMessageInfo
+
+// QueryRemainderResponse defines the response type for Query/Remainder method.
+type QueryRemainderResponse struct {
+ // remainder is the amount backed by the reserve, but not yet owned by any
+ // account, i.e. not in circulation.
+ Remainder types.Coin `protobuf:"bytes,1,opt,name=remainder,proto3" json:"remainder"`
+}
+
+func (m *QueryRemainderResponse) Reset() { *m = QueryRemainderResponse{} }
+func (m *QueryRemainderResponse) String() string { return proto.CompactTextString(m) }
+func (*QueryRemainderResponse) ProtoMessage() {}
+func (*QueryRemainderResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c77e9b73389f3988, []int{3}
+}
+func (m *QueryRemainderResponse) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *QueryRemainderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_QueryRemainderResponse.Marshal(b, m, deterministic)
+ } else {
+ b = b[:cap(b)]
+ n, err := m.MarshalToSizedBuffer(b)
+ if err != nil {
+ return nil, err
+ }
+ return b[:n], nil
+ }
+}
+func (m *QueryRemainderResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_QueryRemainderResponse.Merge(m, src)
+}
+func (m *QueryRemainderResponse) XXX_Size() int {
+ return m.Size()
+}
+func (m *QueryRemainderResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_QueryRemainderResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryRemainderResponse proto.InternalMessageInfo
+
+// QueryFractionalBalanceRequest defines the request type for Query/FractionalBalance method.
+type QueryFractionalBalanceRequest struct {
+ // address is the account address to query fractional balance for.
+ Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+}
+
+func (m *QueryFractionalBalanceRequest) Reset() { *m = QueryFractionalBalanceRequest{} }
+func (m *QueryFractionalBalanceRequest) String() string { return proto.CompactTextString(m) }
+func (*QueryFractionalBalanceRequest) ProtoMessage() {}
+func (*QueryFractionalBalanceRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c77e9b73389f3988, []int{4}
+}
+func (m *QueryFractionalBalanceRequest) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *QueryFractionalBalanceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_QueryFractionalBalanceRequest.Marshal(b, m, deterministic)
+ } else {
+ b = b[:cap(b)]
+ n, err := m.MarshalToSizedBuffer(b)
+ if err != nil {
+ return nil, err
+ }
+ return b[:n], nil
+ }
+}
+func (m *QueryFractionalBalanceRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_QueryFractionalBalanceRequest.Merge(m, src)
+}
+func (m *QueryFractionalBalanceRequest) XXX_Size() int {
+ return m.Size()
+}
+func (m *QueryFractionalBalanceRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_QueryFractionalBalanceRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryFractionalBalanceRequest proto.InternalMessageInfo
+
+// QueryFractionalBalanceResponse defines the response type for Query/FractionalBalance method.
+type QueryFractionalBalanceResponse struct {
+ // fractional_balance is the fractional balance of the address.
+ FractionalBalance types.Coin `protobuf:"bytes,1,opt,name=fractional_balance,json=fractionalBalance,proto3" json:"fractional_balance"`
+}
+
+func (m *QueryFractionalBalanceResponse) Reset() { *m = QueryFractionalBalanceResponse{} }
+func (m *QueryFractionalBalanceResponse) String() string { return proto.CompactTextString(m) }
+func (*QueryFractionalBalanceResponse) ProtoMessage() {}
+func (*QueryFractionalBalanceResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c77e9b73389f3988, []int{5}
+}
+func (m *QueryFractionalBalanceResponse) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *QueryFractionalBalanceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_QueryFractionalBalanceResponse.Marshal(b, m, deterministic)
+ } else {
+ b = b[:cap(b)]
+ n, err := m.MarshalToSizedBuffer(b)
+ if err != nil {
+ return nil, err
+ }
+ return b[:n], nil
+ }
+}
+func (m *QueryFractionalBalanceResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_QueryFractionalBalanceResponse.Merge(m, src)
+}
+func (m *QueryFractionalBalanceResponse) XXX_Size() int {
+ return m.Size()
+}
+func (m *QueryFractionalBalanceResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_QueryFractionalBalanceResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryFractionalBalanceResponse proto.InternalMessageInfo
+
+func init() {
+ proto.RegisterType((*QueryTotalFractionalBalancesRequest)(nil), "zgc.precisebank.v1.QueryTotalFractionalBalancesRequest")
+ proto.RegisterType((*QueryTotalFractionalBalancesResponse)(nil), "zgc.precisebank.v1.QueryTotalFractionalBalancesResponse")
+ proto.RegisterType((*QueryRemainderRequest)(nil), "zgc.precisebank.v1.QueryRemainderRequest")
+ proto.RegisterType((*QueryRemainderResponse)(nil), "zgc.precisebank.v1.QueryRemainderResponse")
+ proto.RegisterType((*QueryFractionalBalanceRequest)(nil), "zgc.precisebank.v1.QueryFractionalBalanceRequest")
+ proto.RegisterType((*QueryFractionalBalanceResponse)(nil), "zgc.precisebank.v1.QueryFractionalBalanceResponse")
+}
+
+func init() { proto.RegisterFile("zgc/precisebank/v1/query.proto", fileDescriptor_c77e9b73389f3988) }
+
+var fileDescriptor_c77e9b73389f3988 = []byte{
+ // 491 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0x4f, 0x6b, 0x14, 0x31,
+ 0x18, 0xc6, 0x67, 0xa4, 0x55, 0x36, 0x9e, 0x1a, 0xd4, 0xd6, 0xa1, 0x46, 0x19, 0x2b, 0xa8, 0x68,
+ 0xb2, 0xb3, 0x2a, 0xd5, 0x83, 0x97, 0x15, 0x3c, 0x89, 0xe0, 0x22, 0x08, 0x82, 0x94, 0x4c, 0x36,
+ 0x4d, 0x07, 0x67, 0x93, 0xe9, 0x24, 0xbb, 0xd8, 0x8a, 0x17, 0x4f, 0x5e, 0x04, 0xc1, 0x8f, 0xe2,
+ 0x37, 0xf0, 0xb4, 0xc7, 0x82, 0x17, 0x4f, 0xa2, 0xbb, 0x7e, 0x10, 0x99, 0x4c, 0x76, 0xd5, 0x9d,
+ 0x4e, 0x99, 0xbd, 0x65, 0xf2, 0xfe, 0x79, 0x7e, 0x6f, 0xde, 0x87, 0x01, 0xe8, 0x50, 0x30, 0x92,
+ 0xe5, 0x9c, 0x25, 0x9a, 0xc7, 0x54, 0xbe, 0x26, 0xa3, 0x88, 0xec, 0x0f, 0x79, 0x7e, 0x80, 0xb3,
+ 0x5c, 0x19, 0x05, 0xe1, 0xa1, 0x60, 0xf8, 0x9f, 0x38, 0x1e, 0x45, 0x01, 0x62, 0x4a, 0x0f, 0x94,
+ 0x26, 0x31, 0xd5, 0x9c, 0x8c, 0xa2, 0x98, 0x1b, 0x1a, 0x11, 0xa6, 0x12, 0x59, 0xd6, 0x04, 0xe7,
+ 0x84, 0x12, 0xca, 0x1e, 0x49, 0x71, 0x72, 0xb7, 0x9b, 0x42, 0x29, 0x91, 0x72, 0x42, 0xb3, 0x84,
+ 0x50, 0x29, 0x95, 0xa1, 0x26, 0x51, 0x52, 0x97, 0xd1, 0xf0, 0x1a, 0xb8, 0xfa, 0xac, 0x90, 0x7d,
+ 0xae, 0x0c, 0x4d, 0x1f, 0xe7, 0x94, 0x15, 0x41, 0x9a, 0x76, 0x69, 0x4a, 0x25, 0xe3, 0xba, 0xc7,
+ 0xf7, 0x87, 0x5c, 0x9b, 0xf0, 0x15, 0xd8, 0x3a, 0x39, 0x4d, 0x67, 0x4a, 0x6a, 0x0e, 0xef, 0x81,
+ 0x55, 0x53, 0xa4, 0x6c, 0xf8, 0x57, 0xfc, 0xeb, 0x67, 0x3b, 0x17, 0x71, 0x89, 0x8c, 0x0b, 0x64,
+ 0xec, 0x90, 0xf1, 0x23, 0x95, 0xc8, 0xee, 0xca, 0xf8, 0xc7, 0x65, 0xaf, 0x57, 0x66, 0x87, 0xeb,
+ 0xe0, 0xbc, 0x6d, 0xdf, 0xe3, 0x03, 0x9a, 0xc8, 0x3e, 0xcf, 0x67, 0xba, 0x2f, 0xc0, 0x85, 0xc5,
+ 0x80, 0x53, 0x7a, 0x08, 0x5a, 0xf9, 0xec, 0xb2, 0xa9, 0xda, 0xdf, 0x8a, 0xf0, 0x01, 0xb8, 0x64,
+ 0x1b, 0x57, 0x66, 0x71, 0xca, 0x70, 0x03, 0x9c, 0xa1, 0xfd, 0x7e, 0xce, 0xb5, 0xb6, 0xdd, 0x5b,
+ 0xbd, 0xd9, 0x67, 0x98, 0x01, 0x54, 0x57, 0xea, 0xd8, 0x9e, 0x02, 0xb8, 0x3b, 0x0f, 0xee, 0xc4,
+ 0x65, 0xb4, 0x29, 0xe4, 0xda, 0xee, 0x62, 0xdf, 0xce, 0xc7, 0x15, 0xb0, 0x6a, 0x25, 0xe1, 0x57,
+ 0x1f, 0xac, 0xd7, 0xec, 0x00, 0x6e, 0xe3, 0xaa, 0x67, 0x70, 0x83, 0xe5, 0x06, 0xf7, 0x97, 0x2f,
+ 0x2c, 0x07, 0x0d, 0xef, 0xbe, 0xff, 0xf6, 0xfb, 0xf3, 0x29, 0x0c, 0x6f, 0x91, 0xb6, 0x58, 0x74,
+ 0xb3, 0xdd, 0xec, 0x4e, 0xf5, 0x1d, 0x34, 0xfc, 0xe0, 0x83, 0xd6, 0x7c, 0xa1, 0xf0, 0x46, 0xad,
+ 0xfa, 0xa2, 0x1b, 0x82, 0x9b, 0x4d, 0x52, 0x1d, 0xda, 0x96, 0x45, 0x43, 0x70, 0xf3, 0x18, 0xb4,
+ 0xb9, 0x0d, 0xe0, 0x17, 0x1f, 0xac, 0x55, 0xe6, 0x83, 0x51, 0xad, 0x4e, 0x9d, 0x5d, 0x82, 0xce,
+ 0x32, 0x25, 0x0e, 0x71, 0xdb, 0x22, 0x46, 0x90, 0x1c, 0x83, 0x58, 0x7d, 0x37, 0xf2, 0xd6, 0x19,
+ 0xf0, 0x5d, 0xf7, 0xc9, 0xf8, 0x17, 0xf2, 0xc6, 0x13, 0xe4, 0x1f, 0x4d, 0x90, 0xff, 0x73, 0x82,
+ 0xfc, 0x4f, 0x53, 0xe4, 0x1d, 0x4d, 0x91, 0xf7, 0x7d, 0x8a, 0xbc, 0x97, 0x58, 0x24, 0x66, 0x6f,
+ 0x18, 0x63, 0xa6, 0x06, 0xa4, 0x2d, 0x52, 0x1a, 0x6b, 0xd2, 0x16, 0xb7, 0xd9, 0x1e, 0x4d, 0x24,
+ 0x79, 0xf3, 0x9f, 0x8e, 0x39, 0xc8, 0xb8, 0x8e, 0x4f, 0xdb, 0x3f, 0xc1, 0x9d, 0x3f, 0x01, 0x00,
+ 0x00, 0xff, 0xff, 0xb3, 0xb3, 0x06, 0x02, 0x93, 0x04, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// QueryClient is the client API for Query service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type QueryClient interface {
+ // TotalFractionalBalances returns the total sum of all fractional balances
+ // managed by the precisebank module.
+ TotalFractionalBalances(ctx context.Context, in *QueryTotalFractionalBalancesRequest, opts ...grpc.CallOption) (*QueryTotalFractionalBalancesResponse, error)
+ // Remainder returns the amount backed by the reserve, but not yet owned by
+ // any account, i.e. not in circulation.
+ Remainder(ctx context.Context, in *QueryRemainderRequest, opts ...grpc.CallOption) (*QueryRemainderResponse, error)
+ // FractionalBalance returns only the fractional balance of an address. This
+ // does not include any integer balance.
+ FractionalBalance(ctx context.Context, in *QueryFractionalBalanceRequest, opts ...grpc.CallOption) (*QueryFractionalBalanceResponse, error)
+}
+
+type queryClient struct {
+ cc grpc1.ClientConn
+}
+
+func NewQueryClient(cc grpc1.ClientConn) QueryClient {
+ return &queryClient{cc}
+}
+
+func (c *queryClient) TotalFractionalBalances(ctx context.Context, in *QueryTotalFractionalBalancesRequest, opts ...grpc.CallOption) (*QueryTotalFractionalBalancesResponse, error) {
+ out := new(QueryTotalFractionalBalancesResponse)
+ err := c.cc.Invoke(ctx, "/zgc.precisebank.v1.Query/TotalFractionalBalances", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *queryClient) Remainder(ctx context.Context, in *QueryRemainderRequest, opts ...grpc.CallOption) (*QueryRemainderResponse, error) {
+ out := new(QueryRemainderResponse)
+ err := c.cc.Invoke(ctx, "/zgc.precisebank.v1.Query/Remainder", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *queryClient) FractionalBalance(ctx context.Context, in *QueryFractionalBalanceRequest, opts ...grpc.CallOption) (*QueryFractionalBalanceResponse, error) {
+ out := new(QueryFractionalBalanceResponse)
+ err := c.cc.Invoke(ctx, "/zgc.precisebank.v1.Query/FractionalBalance", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// QueryServer is the server API for Query service.
+type QueryServer interface {
+ // TotalFractionalBalances returns the total sum of all fractional balances
+ // managed by the precisebank module.
+ TotalFractionalBalances(context.Context, *QueryTotalFractionalBalancesRequest) (*QueryTotalFractionalBalancesResponse, error)
+ // Remainder returns the amount backed by the reserve, but not yet owned by
+ // any account, i.e. not in circulation.
+ Remainder(context.Context, *QueryRemainderRequest) (*QueryRemainderResponse, error)
+ // FractionalBalance returns only the fractional balance of an address. This
+ // does not include any integer balance.
+ FractionalBalance(context.Context, *QueryFractionalBalanceRequest) (*QueryFractionalBalanceResponse, error)
+}
+
+// UnimplementedQueryServer can be embedded to have forward compatible implementations.
+type UnimplementedQueryServer struct {
+}
+
+func (*UnimplementedQueryServer) TotalFractionalBalances(ctx context.Context, req *QueryTotalFractionalBalancesRequest) (*QueryTotalFractionalBalancesResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method TotalFractionalBalances not implemented")
+}
+func (*UnimplementedQueryServer) Remainder(ctx context.Context, req *QueryRemainderRequest) (*QueryRemainderResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method Remainder not implemented")
+}
+func (*UnimplementedQueryServer) FractionalBalance(ctx context.Context, req *QueryFractionalBalanceRequest) (*QueryFractionalBalanceResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method FractionalBalance not implemented")
+}
+
+func RegisterQueryServer(s grpc1.Server, srv QueryServer) {
+ s.RegisterService(&_Query_serviceDesc, srv)
+}
+
+func _Query_TotalFractionalBalances_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(QueryTotalFractionalBalancesRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(QueryServer).TotalFractionalBalances(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/zgc.precisebank.v1.Query/TotalFractionalBalances",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(QueryServer).TotalFractionalBalances(ctx, req.(*QueryTotalFractionalBalancesRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _Query_Remainder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(QueryRemainderRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(QueryServer).Remainder(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/zgc.precisebank.v1.Query/Remainder",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(QueryServer).Remainder(ctx, req.(*QueryRemainderRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _Query_FractionalBalance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(QueryFractionalBalanceRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(QueryServer).FractionalBalance(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/zgc.precisebank.v1.Query/FractionalBalance",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(QueryServer).FractionalBalance(ctx, req.(*QueryFractionalBalanceRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+var _Query_serviceDesc = grpc.ServiceDesc{
+ ServiceName: "zgc.precisebank.v1.Query",
+ HandlerType: (*QueryServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "TotalFractionalBalances",
+ Handler: _Query_TotalFractionalBalances_Handler,
+ },
+ {
+ MethodName: "Remainder",
+ Handler: _Query_Remainder_Handler,
+ },
+ {
+ MethodName: "FractionalBalance",
+ Handler: _Query_FractionalBalance_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "zgc/precisebank/v1/query.proto",
+}
+
+func (m *QueryTotalFractionalBalancesRequest) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBuffer(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *QueryTotalFractionalBalancesRequest) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryTotalFractionalBalancesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ return len(dAtA) - i, nil
+}
+
+func (m *QueryTotalFractionalBalancesResponse) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBuffer(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *QueryTotalFractionalBalancesResponse) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryTotalFractionalBalancesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ {
+ size, err := m.Total.MarshalToSizedBuffer(dAtA[:i])
+ if err != nil {
+ return 0, err
+ }
+ i -= size
+ i = encodeVarintQuery(dAtA, i, uint64(size))
+ }
+ i--
+ dAtA[i] = 0xa
+ return len(dAtA) - i, nil
+}
+
+func (m *QueryRemainderRequest) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBuffer(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *QueryRemainderRequest) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryRemainderRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ return len(dAtA) - i, nil
+}
+
+func (m *QueryRemainderResponse) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBuffer(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *QueryRemainderResponse) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryRemainderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ {
+ size, err := m.Remainder.MarshalToSizedBuffer(dAtA[:i])
+ if err != nil {
+ return 0, err
+ }
+ i -= size
+ i = encodeVarintQuery(dAtA, i, uint64(size))
+ }
+ i--
+ dAtA[i] = 0xa
+ return len(dAtA) - i, nil
+}
+
+func (m *QueryFractionalBalanceRequest) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBuffer(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *QueryFractionalBalanceRequest) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryFractionalBalanceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ if len(m.Address) > 0 {
+ i -= len(m.Address)
+ copy(dAtA[i:], m.Address)
+ i = encodeVarintQuery(dAtA, i, uint64(len(m.Address)))
+ i--
+ dAtA[i] = 0xa
+ }
+ return len(dAtA) - i, nil
+}
+
+func (m *QueryFractionalBalanceResponse) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBuffer(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *QueryFractionalBalanceResponse) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryFractionalBalanceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ {
+ size, err := m.FractionalBalance.MarshalToSizedBuffer(dAtA[:i])
+ if err != nil {
+ return 0, err
+ }
+ i -= size
+ i = encodeVarintQuery(dAtA, i, uint64(size))
+ }
+ i--
+ dAtA[i] = 0xa
+ return len(dAtA) - i, nil
+}
+
+func encodeVarintQuery(dAtA []byte, offset int, v uint64) int {
+ offset -= sovQuery(v)
+ base := offset
+ for v >= 1<<7 {
+ dAtA[offset] = uint8(v&0x7f | 0x80)
+ v >>= 7
+ offset++
+ }
+ dAtA[offset] = uint8(v)
+ return base
+}
+func (m *QueryTotalFractionalBalancesRequest) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ return n
+}
+
+func (m *QueryTotalFractionalBalancesResponse) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = m.Total.Size()
+ n += 1 + l + sovQuery(uint64(l))
+ return n
+}
+
+func (m *QueryRemainderRequest) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ return n
+}
+
+func (m *QueryRemainderResponse) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = m.Remainder.Size()
+ n += 1 + l + sovQuery(uint64(l))
+ return n
+}
+
+func (m *QueryFractionalBalanceRequest) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = len(m.Address)
+ if l > 0 {
+ n += 1 + l + sovQuery(uint64(l))
+ }
+ return n
+}
+
+func (m *QueryFractionalBalanceResponse) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = m.FractionalBalance.Size()
+ n += 1 + l + sovQuery(uint64(l))
+ return n
+}
+
+func sovQuery(x uint64) (n int) {
+ return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozQuery(x uint64) (n int) {
+ return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *QueryTotalFractionalBalancesRequest) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: QueryTotalFractionalBalancesRequest: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: QueryTotalFractionalBalancesRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ default:
+ iNdEx = preIndex
+ skippy, err := skipQuery(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *QueryTotalFractionalBalancesResponse) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: QueryTotalFractionalBalancesResponse: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: QueryTotalFractionalBalancesResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Total", wireType)
+ }
+ var msglen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ msglen |= int(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if msglen < 0 {
+ return ErrInvalidLengthQuery
+ }
+ postIndex := iNdEx + msglen
+ if postIndex < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ if err := m.Total.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipQuery(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *QueryRemainderRequest) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: QueryRemainderRequest: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: QueryRemainderRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ default:
+ iNdEx = preIndex
+ skippy, err := skipQuery(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *QueryRemainderResponse) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: QueryRemainderResponse: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: QueryRemainderResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Remainder", wireType)
+ }
+ var msglen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ msglen |= int(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if msglen < 0 {
+ return ErrInvalidLengthQuery
+ }
+ postIndex := iNdEx + msglen
+ if postIndex < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ if err := m.Remainder.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipQuery(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *QueryFractionalBalanceRequest) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: QueryFractionalBalanceRequest: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: QueryFractionalBalanceRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthQuery
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Address = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipQuery(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func (m *QueryFractionalBalanceResponse) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: QueryFractionalBalanceResponse: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: QueryFractionalBalanceResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field FractionalBalance", wireType)
+ }
+ var msglen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ msglen |= int(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if msglen < 0 {
+ return ErrInvalidLengthQuery
+ }
+ postIndex := iNdEx + msglen
+ if postIndex < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ if err := m.FractionalBalance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipQuery(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return ErrInvalidLengthQuery
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
+func skipQuery(dAtA []byte) (n int, err error) {
+ l := len(dAtA)
+ iNdEx := 0
+ depth := 0
+ for iNdEx < l {
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= (uint64(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ wireType := int(wire & 0x7)
+ switch wireType {
+ case 0:
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ iNdEx++
+ if dAtA[iNdEx-1] < 0x80 {
+ break
+ }
+ }
+ case 1:
+ iNdEx += 8
+ case 2:
+ var length int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return 0, ErrIntOverflowQuery
+ }
+ if iNdEx >= l {
+ return 0, io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ length |= (int(b) & 0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if length < 0 {
+ return 0, ErrInvalidLengthQuery
+ }
+ iNdEx += length
+ case 3:
+ depth++
+ case 4:
+ if depth == 0 {
+ return 0, ErrUnexpectedEndOfGroupQuery
+ }
+ depth--
+ case 5:
+ iNdEx += 4
+ default:
+ return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+ }
+ if iNdEx < 0 {
+ return 0, ErrInvalidLengthQuery
+ }
+ if depth == 0 {
+ return iNdEx, nil
+ }
+ }
+ return 0, io.ErrUnexpectedEOF
+}
+
+var (
+ ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling")
+ ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow")
+ ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/x/precisebank/types/query.pb.gw.go b/x/precisebank/types/query.pb.gw.go
new file mode 100644
index 00000000..74cce9be
--- /dev/null
+++ b/x/precisebank/types/query.pb.gw.go
@@ -0,0 +1,319 @@
+// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
+// source: zgc/precisebank/v1/query.proto
+
+/*
+Package types is a reverse proxy.
+
+It translates gRPC into RESTful JSON APIs.
+*/
+package types
+
+import (
+ "context"
+ "io"
+ "net/http"
+
+ "github.com/golang/protobuf/descriptor"
+ "github.com/golang/protobuf/proto"
+ "github.com/grpc-ecosystem/grpc-gateway/runtime"
+ "github.com/grpc-ecosystem/grpc-gateway/utilities"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/grpclog"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc/status"
+)
+
+// Suppress "imported and not used" errors
+var _ codes.Code
+var _ io.Reader
+var _ status.Status
+var _ = runtime.String
+var _ = utilities.NewDoubleArray
+var _ = descriptor.ForMessage
+var _ = metadata.Join
+
+func request_Query_TotalFractionalBalances_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq QueryTotalFractionalBalancesRequest
+ var metadata runtime.ServerMetadata
+
+ msg, err := client.TotalFractionalBalances(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+ return msg, metadata, err
+
+}
+
+func local_request_Query_TotalFractionalBalances_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq QueryTotalFractionalBalancesRequest
+ var metadata runtime.ServerMetadata
+
+ msg, err := server.TotalFractionalBalances(ctx, &protoReq)
+ return msg, metadata, err
+
+}
+
+func request_Query_Remainder_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq QueryRemainderRequest
+ var metadata runtime.ServerMetadata
+
+ msg, err := client.Remainder(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+ return msg, metadata, err
+
+}
+
+func local_request_Query_Remainder_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq QueryRemainderRequest
+ var metadata runtime.ServerMetadata
+
+ msg, err := server.Remainder(ctx, &protoReq)
+ return msg, metadata, err
+
+}
+
+func request_Query_FractionalBalance_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq QueryFractionalBalanceRequest
+ var metadata runtime.ServerMetadata
+
+ var (
+ val string
+ ok bool
+ err error
+ _ = err
+ )
+
+ val, ok = pathParams["address"]
+ if !ok {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address")
+ }
+
+ protoReq.Address, err = runtime.String(val)
+
+ if err != nil {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err)
+ }
+
+ msg, err := client.FractionalBalance(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+ return msg, metadata, err
+
+}
+
+func local_request_Query_FractionalBalance_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq QueryFractionalBalanceRequest
+ var metadata runtime.ServerMetadata
+
+ var (
+ val string
+ ok bool
+ err error
+ _ = err
+ )
+
+ val, ok = pathParams["address"]
+ if !ok {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address")
+ }
+
+ protoReq.Address, err = runtime.String(val)
+
+ if err != nil {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err)
+ }
+
+ msg, err := server.FractionalBalance(ctx, &protoReq)
+ return msg, metadata, err
+
+}
+
+// RegisterQueryHandlerServer registers the http handlers for service Query to "mux".
+// UnaryRPC :call QueryServer directly.
+// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
+// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead.
+func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error {
+
+ mux.Handle("GET", pattern_Query_TotalFractionalBalances_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+ ctx, cancel := context.WithCancel(req.Context())
+ defer cancel()
+ var stream runtime.ServerTransportStream
+ ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
+ inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+ rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := local_request_Query_TotalFractionalBalances_0(rctx, inboundMarshaler, server, req, pathParams)
+ md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
+ ctx = runtime.NewServerMetadataContext(ctx, md)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+
+ forward_Query_TotalFractionalBalances_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ mux.Handle("GET", pattern_Query_Remainder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+ ctx, cancel := context.WithCancel(req.Context())
+ defer cancel()
+ var stream runtime.ServerTransportStream
+ ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
+ inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+ rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := local_request_Query_Remainder_0(rctx, inboundMarshaler, server, req, pathParams)
+ md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
+ ctx = runtime.NewServerMetadataContext(ctx, md)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+
+ forward_Query_Remainder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ mux.Handle("GET", pattern_Query_FractionalBalance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+ ctx, cancel := context.WithCancel(req.Context())
+ defer cancel()
+ var stream runtime.ServerTransportStream
+ ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
+ inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+ rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := local_request_Query_FractionalBalance_0(rctx, inboundMarshaler, server, req, pathParams)
+ md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
+ ctx = runtime.NewServerMetadataContext(ctx, md)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+
+ forward_Query_FractionalBalance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ return nil
+}
+
+// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but
+// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
+func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
+ conn, err := grpc.Dial(endpoint, opts...)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ if cerr := conn.Close(); cerr != nil {
+ grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
+ }
+ return
+ }
+ go func() {
+ <-ctx.Done()
+ if cerr := conn.Close(); cerr != nil {
+ grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
+ }
+ }()
+ }()
+
+ return RegisterQueryHandler(ctx, mux, conn)
+}
+
+// RegisterQueryHandler registers the http handlers for service Query to "mux".
+// The handlers forward requests to the grpc endpoint over "conn".
+func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
+ return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn))
+}
+
+// RegisterQueryHandlerClient registers the http handlers for service Query
+// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient".
+// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient"
+// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
+// "QueryClient" to call the correct interceptors.
+func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error {
+
+ mux.Handle("GET", pattern_Query_TotalFractionalBalances_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+ ctx, cancel := context.WithCancel(req.Context())
+ defer cancel()
+ inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+ rctx, err := runtime.AnnotateContext(ctx, mux, req)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := request_Query_TotalFractionalBalances_0(rctx, inboundMarshaler, client, req, pathParams)
+ ctx = runtime.NewServerMetadataContext(ctx, md)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+
+ forward_Query_TotalFractionalBalances_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ mux.Handle("GET", pattern_Query_Remainder_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+ ctx, cancel := context.WithCancel(req.Context())
+ defer cancel()
+ inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+ rctx, err := runtime.AnnotateContext(ctx, mux, req)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := request_Query_Remainder_0(rctx, inboundMarshaler, client, req, pathParams)
+ ctx = runtime.NewServerMetadataContext(ctx, md)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+
+ forward_Query_Remainder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ mux.Handle("GET", pattern_Query_FractionalBalance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+ ctx, cancel := context.WithCancel(req.Context())
+ defer cancel()
+ inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+ rctx, err := runtime.AnnotateContext(ctx, mux, req)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := request_Query_FractionalBalance_0(rctx, inboundMarshaler, client, req, pathParams)
+ ctx = runtime.NewServerMetadataContext(ctx, md)
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+
+ forward_Query_FractionalBalance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+ })
+
+ return nil
+}
+
+var (
+ pattern_Query_TotalFractionalBalances_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"0g", "precisebank", "v1", "total_fractional_balances"}, "", runtime.AssumeColonVerbOpt(true)))
+
+ pattern_Query_Remainder_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"0g", "precisebank", "v1", "remainder"}, "", runtime.AssumeColonVerbOpt(true)))
+
+ pattern_Query_FractionalBalance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"0g", "precisebank", "v1", "fractional_balance", "address"}, "", runtime.AssumeColonVerbOpt(true)))
+)
+
+var (
+ forward_Query_TotalFractionalBalances_0 = runtime.ForwardResponseMessage
+
+ forward_Query_Remainder_0 = runtime.ForwardResponseMessage
+
+ forward_Query_FractionalBalance_0 = runtime.ForwardResponseMessage
+)
diff --git a/x/pricefeed/abci.go b/x/pricefeed/abci.go
index f590f497..a1ad752f 100644
--- a/x/pricefeed/abci.go
+++ b/x/pricefeed/abci.go
@@ -1,7 +1,6 @@
package pricefeed
import (
- "errors"
"time"
"github.com/0glabs/0g-chain/x/pricefeed/keeper"
@@ -14,15 +13,5 @@ import (
func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)
- // Update the current price of each asset.
- for _, market := range k.GetMarkets(ctx) {
- if !market.Active {
- continue
- }
-
- err := k.SetCurrentPrices(ctx, market.MarketID)
- if err != nil && !errors.Is(err, types.ErrNoValidPrice) {
- panic(err)
- }
- }
+ k.SetCurrentPricesForAllMarkets(ctx)
}
diff --git a/x/pricefeed/abci_test.go b/x/pricefeed/abci_test.go
new file mode 100644
index 00000000..9de40a55
--- /dev/null
+++ b/x/pricefeed/abci_test.go
@@ -0,0 +1,20 @@
+package pricefeed_test
+
+import (
+ "testing"
+
+ "github.com/0glabs/0g-chain/x/pricefeed"
+ "github.com/0glabs/0g-chain/x/pricefeed/keeper"
+ "github.com/0glabs/0g-chain/x/pricefeed/testutil"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+func TestEndBlocker_UpdatesMultipleMarkets(t *testing.T) {
+ testutil.SetCurrentPrices_PriceCalculations(t, func(ctx sdk.Context, keeper keeper.Keeper) {
+ pricefeed.EndBlocker(ctx, keeper)
+ })
+
+ testutil.SetCurrentPrices_EventEmission(t, func(ctx sdk.Context, keeper keeper.Keeper) {
+ pricefeed.EndBlocker(ctx, keeper)
+ })
+}
diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go
index a99cdd1c..7bd0a700 100644
--- a/x/pricefeed/keeper/keeper.go
+++ b/x/pricefeed/keeper/keeper.go
@@ -131,6 +131,74 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error {
return nil
}
+// SetCurrentPricesForAllMarkets updates the price of an asset to the median of all valid oracle inputs
+func (k Keeper) SetCurrentPricesForAllMarkets(ctx sdk.Context) {
+ orderedMarkets := []string{}
+ marketPricesByID := make(map[string]types.CurrentPrices)
+
+ for _, market := range k.GetMarkets(ctx) {
+ if market.Active {
+ orderedMarkets = append(orderedMarkets, market.MarketID)
+ marketPricesByID[market.MarketID] = types.CurrentPrices{}
+ }
+ }
+
+ iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.RawPriceFeedPrefix)
+ for ; iterator.Valid(); iterator.Next() {
+ var postedPrice types.PostedPrice
+ k.cdc.MustUnmarshal(iterator.Value(), &postedPrice)
+
+ prices, found := marketPricesByID[postedPrice.MarketID]
+ if !found {
+ continue
+ }
+
+ // filter out expired prices
+ if postedPrice.Expiry.After(ctx.BlockTime()) {
+ marketPricesByID[postedPrice.MarketID] = append(prices, types.NewCurrentPrice(postedPrice.MarketID, postedPrice.Price))
+ }
+ }
+ iterator.Close()
+
+ for _, marketID := range orderedMarkets {
+ // store current price
+ validPrevPrice := true
+ prevPrice, err := k.GetCurrentPrice(ctx, marketID)
+ if err != nil {
+ validPrevPrice = false
+ }
+
+ notExpiredPrices, _ := marketPricesByID[marketID]
+
+ if len(notExpiredPrices) == 0 {
+ // NOTE: The current price stored will continue storing the most recent (expired)
+ // price if this is not set.
+ // This zero's out the current price stored value for that market and ensures
+ // that CDP methods that GetCurrentPrice will return error.
+ k.setCurrentPrice(ctx, marketID, types.CurrentPrice{})
+ continue
+ }
+
+ medianPrice := k.CalculateMedianPrice(notExpiredPrices)
+
+ // check case that market price was not set in genesis
+ //if validPrevPrice && !medianPrice.Equal(prevPrice.Price) {
+ if validPrevPrice && !medianPrice.Equal(prevPrice.Price) {
+ // only emit event if price has changed
+ ctx.EventManager().EmitEvent(
+ sdk.NewEvent(
+ types.EventTypeMarketPriceUpdated,
+ sdk.NewAttribute(types.AttributeMarketID, marketID),
+ sdk.NewAttribute(types.AttributeMarketPrice, medianPrice.String()),
+ ),
+ )
+ }
+
+ currentPrice := types.NewCurrentPrice(marketID, medianPrice)
+ k.setCurrentPrice(ctx, marketID, currentPrice)
+ }
+}
+
func (k Keeper) setCurrentPrice(ctx sdk.Context, marketID string, currentPrice types.CurrentPrice) {
store := ctx.KVStore(k.key)
store.Set(types.CurrentPriceKey(marketID), k.cdc.MustMarshal(¤tPrice))
diff --git a/x/pricefeed/keeper/keeper_test.go b/x/pricefeed/keeper/keeper_test.go
index 9a2dc138..13bbd079 100644
--- a/x/pricefeed/keeper/keeper_test.go
+++ b/x/pricefeed/keeper/keeper_test.go
@@ -1,6 +1,7 @@
package keeper_test
import (
+ "errors"
"testing"
"time"
@@ -236,3 +237,33 @@ func TestKeeper_ExpiredSetCurrentPrices(t *testing.T) {
_, err = keeper.GetCurrentPrice(ctx, "tstusd")
require.ErrorIs(t, types.ErrNoValidPrice, err, "current prices should be invalid")
}
+
+func TestKeeper_SetCurrentPricesForAllMarkets_PriceUpdate(t *testing.T) {
+ testutil.SetCurrentPrices_PriceCalculations(t, func(ctx sdk.Context, keeper keeper.Keeper) {
+ keeper.SetCurrentPricesForAllMarkets(ctx)
+ })
+}
+
+func TestKeeper_SetCurrentPricesForAllMarkets_EventEmission(t *testing.T) {
+ testutil.SetCurrentPrices_EventEmission(t, func(ctx sdk.Context, keeper keeper.Keeper) {
+ keeper.SetCurrentPricesForAllMarkets(ctx)
+ })
+}
+
+func TestKeeper_SetCurrentPrices_MatchesAllMarketsBehavior(t *testing.T) {
+ testFunc := func(ctx sdk.Context, k keeper.Keeper) {
+ for _, market := range k.GetMarkets(ctx) {
+ if !market.Active {
+ continue
+ }
+
+ err := k.SetCurrentPrices(ctx, market.MarketID)
+ if err != nil && !errors.Is(err, types.ErrNoValidPrice) {
+ panic(err)
+ }
+ }
+ }
+
+ testutil.SetCurrentPrices_PriceCalculations(t, testFunc)
+ testutil.SetCurrentPrices_EventEmission(t, testFunc)
+}
diff --git a/x/pricefeed/testutil/helpers.go b/x/pricefeed/testutil/helpers.go
new file mode 100644
index 00000000..5c1465eb
--- /dev/null
+++ b/x/pricefeed/testutil/helpers.go
@@ -0,0 +1,236 @@
+package testutil
+
+import (
+ "testing"
+ "time"
+
+ tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/0glabs/0g-chain/app"
+ "github.com/0glabs/0g-chain/x/pricefeed/keeper"
+ "github.com/0glabs/0g-chain/x/pricefeed/types"
+)
+
+func SetCurrentPrices_PriceCalculations(t *testing.T, f func(ctx sdk.Context, keeper keeper.Keeper)) {
+ _, addrs := app.GeneratePrivKeyAddressPairs(5)
+ tApp := app.NewTestApp()
+ ctx := tApp.NewContext(true, tmprototypes.Header{}).
+ WithBlockTime(time.Now().UTC())
+ keeper := tApp.GetPriceFeedKeeper()
+
+ params := types.Params{
+ Markets: []types.Market{
+ // valid previous price, expired prices, price change, and active
+ {MarketID: "asset1:usd", BaseAsset: "asset1", QuoteAsset: "usd", Oracles: addrs, Active: true},
+ // same data as asset1, but not active and should be ignored
+ {MarketID: "asset2:usd", BaseAsset: "asset2", QuoteAsset: "usd", Oracles: addrs, Active: false},
+ // same data as asset1 except no valid previous price
+ {MarketID: "asset3:usd", BaseAsset: "asset3", QuoteAsset: "usd", Oracles: addrs, Active: true},
+ // previous price set, but no valid prices
+ {MarketID: "asset4:usd", BaseAsset: "asset4", QuoteAsset: "usd", Oracles: addrs, Active: true},
+ // same as market one except different prices
+ {MarketID: "asset5:usd", BaseAsset: "asset5", QuoteAsset: "usd", Oracles: addrs, Active: true},
+ },
+ }
+ keeper.SetParams(ctx, params)
+
+ // need price equal to block time and after block time
+ blockTime := time.Now()
+ initialPriceExpiry := blockTime.Add(1 * time.Hour)
+
+ _, err := keeper.SetPrice(ctx, addrs[0], "asset1:usd", sdk.MustNewDecFromStr("1"), initialPriceExpiry)
+ require.NoError(t, err)
+ _, err = keeper.SetPrice(ctx, addrs[0], "asset2:usd", sdk.MustNewDecFromStr("1"), initialPriceExpiry)
+ require.NoError(t, err)
+ _, err = keeper.SetPrice(ctx, addrs[0], "asset4:usd", sdk.MustNewDecFromStr("1"), initialPriceExpiry)
+ require.NoError(t, err)
+ _, err = keeper.SetPrice(ctx, addrs[0], "asset5:usd", sdk.MustNewDecFromStr("10"), initialPriceExpiry)
+ require.NoError(t, err)
+
+ ctx = ctx.WithBlockTime(blockTime)
+ f(ctx, keeper)
+
+ // price should be set
+ price, err := keeper.GetCurrentPrice(ctx, "asset1:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.OneDec(), price.Price)
+ // not an active market, so price is not set
+ price, err = keeper.GetCurrentPrice(ctx, "asset2:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ // no price posted
+ price, err = keeper.GetCurrentPrice(ctx, "asset3:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ // price set initially
+ price, err = keeper.GetCurrentPrice(ctx, "asset4:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.OneDec(), price.Price)
+ price, err = keeper.GetCurrentPrice(ctx, "asset5:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.MustNewDecFromStr("10.0"), price.Price)
+
+ _, err = keeper.SetPrice(ctx, addrs[1], "asset1:usd", sdk.MustNewDecFromStr("2"), initialPriceExpiry.Add(1*time.Hour))
+ require.NoError(t, err)
+ _, err = keeper.SetPrice(ctx, addrs[1], "asset2:usd", sdk.MustNewDecFromStr("2"), initialPriceExpiry.Add(1*time.Hour))
+ require.NoError(t, err)
+ _, err = keeper.SetPrice(ctx, addrs[1], "asset5:usd", sdk.MustNewDecFromStr("20"), initialPriceExpiry.Add(1*time.Hour))
+ require.NoError(t, err)
+
+ blockTime = blockTime.Add(30 * time.Minute)
+ ctx = ctx.WithBlockTime(blockTime)
+ f(ctx, keeper)
+
+ // price should be set
+ price, err = keeper.GetCurrentPrice(ctx, "asset1:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.MustNewDecFromStr("1.5"), price.Price)
+ // not an active market, so price is not set
+ price, err = keeper.GetCurrentPrice(ctx, "asset2:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ // no price posted
+ price, err = keeper.GetCurrentPrice(ctx, "asset3:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ // price set initially
+ price, err = keeper.GetCurrentPrice(ctx, "asset4:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.OneDec(), price.Price)
+ price, err = keeper.GetCurrentPrice(ctx, "asset5:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.MustNewDecFromStr("15.0"), price.Price)
+
+ _, err = keeper.SetPrice(ctx, addrs[2], "asset1:usd", sdk.MustNewDecFromStr("30"), initialPriceExpiry.Add(1*time.Hour))
+ require.NoError(t, err)
+ _, err = keeper.SetPrice(ctx, addrs[2], "asset2:usd", sdk.MustNewDecFromStr("30"), initialPriceExpiry.Add(1*time.Hour))
+ require.NoError(t, err)
+ _, err = keeper.SetPrice(ctx, addrs[2], "asset5:usd", sdk.MustNewDecFromStr("30"), initialPriceExpiry.Add(1*time.Hour))
+ require.NoError(t, err)
+
+ blockTime = blockTime.Add(15 * time.Minute)
+ ctx = ctx.WithBlockTime(blockTime)
+ f(ctx, keeper)
+
+ // price should be set
+ price, err = keeper.GetCurrentPrice(ctx, "asset1:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.MustNewDecFromStr("2.0"), price.Price)
+ // not an active market, so price is not set
+ price, err = keeper.GetCurrentPrice(ctx, "asset2:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ // no price posted
+ price, err = keeper.GetCurrentPrice(ctx, "asset3:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ // price set initially
+ price, err = keeper.GetCurrentPrice(ctx, "asset4:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.OneDec(), price.Price)
+ price, err = keeper.GetCurrentPrice(ctx, "asset5:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.MustNewDecFromStr("20.0"), price.Price)
+
+ blockTime = blockTime.Add(15 * time.Minute)
+ ctx = ctx.WithBlockTime(blockTime)
+ f(ctx, keeper)
+
+ // price should be set
+ price, err = keeper.GetCurrentPrice(ctx, "asset1:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.MustNewDecFromStr("16"), price.Price)
+ // not an active market, so price is not set
+ price, err = keeper.GetCurrentPrice(ctx, "asset2:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ // no price posted
+ price, err = keeper.GetCurrentPrice(ctx, "asset3:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ // price set initially, now expired
+ price, err = keeper.GetCurrentPrice(ctx, "asset4:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ price, err = keeper.GetCurrentPrice(ctx, "asset5:usd")
+ require.NoError(t, err)
+ require.Equal(t, sdk.MustNewDecFromStr("25.0"), price.Price)
+
+ blockTime = blockTime.Add(10 * time.Hour)
+ ctx = ctx.WithBlockTime(blockTime)
+ f(ctx, keeper)
+
+ // all prices expired now
+ price, err = keeper.GetCurrentPrice(ctx, "asset1:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ price, err = keeper.GetCurrentPrice(ctx, "asset2:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ price, err = keeper.GetCurrentPrice(ctx, "asset3:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ price, err = keeper.GetCurrentPrice(ctx, "asset4:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+ price, err = keeper.GetCurrentPrice(ctx, "asset5:usd")
+ require.Equal(t, types.ErrNoValidPrice, err)
+}
+
+func SetCurrentPrices_EventEmission(t *testing.T, f func(ctx sdk.Context, keeper keeper.Keeper)) {
+ _, addrs := app.GeneratePrivKeyAddressPairs(5)
+ tApp := app.NewTestApp()
+ ctx := tApp.NewContext(true, tmprototypes.Header{}).
+ WithBlockTime(time.Now().UTC())
+ keeper := tApp.GetPriceFeedKeeper()
+
+ params := types.Params{
+ Markets: []types.Market{
+ {MarketID: "asset1:usd", BaseAsset: "asset1", QuoteAsset: "usd", Oracles: addrs, Active: true},
+ },
+ }
+ keeper.SetParams(ctx, params)
+
+ blockTime := time.Now()
+ initialPriceExpiry := blockTime.Add(1 * time.Hour)
+
+ // post a price
+ _, err := keeper.SetPrice(ctx, addrs[0], "asset1:usd", sdk.MustNewDecFromStr("1"), initialPriceExpiry)
+ require.NoError(t, err)
+
+ // reset context with fresh event manager
+ ctx = ctx.WithBlockTime(blockTime).WithEventManager(sdk.NewEventManager())
+ f(ctx, keeper)
+
+ // no previous price so no event
+ require.Equal(t, 0, len(ctx.EventManager().Events()))
+
+ // post same price from another oracle
+ _, err = keeper.SetPrice(ctx, addrs[1], "asset1:usd", sdk.MustNewDecFromStr("1"), initialPriceExpiry)
+ require.NoError(t, err)
+
+ blockTime = blockTime.Add(10 * time.Second)
+ ctx = ctx.WithBlockTime(blockTime).WithEventManager(sdk.NewEventManager())
+ f(ctx, keeper)
+
+ // no price change so no event
+ require.Equal(t, 0, len(ctx.EventManager().Events()))
+
+ // post price changes
+ _, err = keeper.SetPrice(ctx, addrs[2], "asset1:usd", sdk.MustNewDecFromStr("2"), initialPriceExpiry)
+ require.NoError(t, err)
+ _, err = keeper.SetPrice(ctx, addrs[3], "asset1:usd", sdk.MustNewDecFromStr("10"), initialPriceExpiry)
+ require.NoError(t, err)
+ _, err = keeper.SetPrice(ctx, addrs[4], "asset1:usd", sdk.MustNewDecFromStr("10"), initialPriceExpiry)
+ require.NoError(t, err)
+
+ blockTime = blockTime.Add(10 * time.Second)
+ ctx = ctx.WithBlockTime(blockTime).WithEventManager(sdk.NewEventManager())
+ f(ctx, keeper)
+
+ // price is changes so event should be emitted
+ require.Equal(t, 1, len(ctx.EventManager().Events()))
+
+ event := ctx.EventManager().Events()[0]
+
+ // has correct event type
+ assert.Equal(t, types.EventTypeMarketPriceUpdated, event.Type)
+ // has correct attributes
+ marketID, found := event.GetAttribute(types.AttributeMarketID)
+ require.True(t, found)
+ marketPrice, found := event.GetAttribute(types.AttributeMarketPrice)
+ require.True(t, found)
+ // attributes have correct values
+ assert.Equal(t, "asset1:usd", marketID.Value)
+ assert.Equal(t, sdk.MustNewDecFromStr("2").String(), marketPrice.Value)
+}