From 4a73c373961ff8a1e7d3c1944a8d17916166c881 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Tue, 25 Jun 2024 22:27:33 -0500 Subject: [PATCH 1/3] ci(docker): fix chaosnet and add profiles (#1937) * chore: delete multi-node docker compose file * fix: enable gRPC server * feat: add profiles and remove liquidator/faucet * fix: force go-heartmonitor linux/amd64 * feat: add make commands for chaosnet-ibc and chaosnet-heartmonitor * chore: update docs --- CHAOSNET.md | 21 +++--- contrib/docker-compose/README.md | 67 ++++++++++++------- .../docker-compose-chaosnet.yml | 43 +++--------- .../docker-compose-multi-node.yml | 64 ------------------ contrib/make/chaosnet.mk | 30 ++++++--- contrib/scripts/chaosnet.sh | 1 + 6 files changed, 87 insertions(+), 139 deletions(-) delete mode 100644 contrib/docker-compose/docker-compose-multi-node.yml diff --git a/CHAOSNET.md b/CHAOSNET.md index 3910d4434..8a9e802fa 100644 --- a/CHAOSNET.md +++ b/CHAOSNET.md @@ -17,20 +17,18 @@ Chaosnet is an expanded version of localnet that runs: -- two validators (nibiru-0 and nibiru-1) +- up to two validators (nibiru-0 and nibiru-1) - pricefeeders for each validator -- a hermes relayer between the two validators -- a faucet -- a postgres:14 database -- a heartmonitor instance -- a liquidator instance -- a graphql server +- a hermes relayer between the two validators (if the `ibc` profile is used) +- a postgres:14 database (if the `heartmonitor` profile is used) +- a heartmonitor instance (if the `heartmonitor` profile is used) +- a graphql server (if the `heartmonitor` profile is used) ## How to run "chaosnet" 1. Make sure you have [Docker](https://docs.docker.com/engine/install/) installed and running 2. Make sure you have `make` installed -3. Docker login to ghcr.io +3. (Optional) Docker login to ghcr.io (only if you want to use the `heartmonitor` profile) ```bash docker login ghcr.io @@ -42,9 +40,14 @@ Enter your GitHub username for the `username` field, and your personal access to Note that this will take a while the first time you run it, as it will need to pull all the images from the registry, build the chaonset image locally, and set up the IBC channel (which has a lot of round trip packet commits). +Other profiles include + +- `make chaosnet-ibc`: creates two validator instances and a hermes relayer +- `make chaosnet-heartmonitor`: single validator with heartmonitor+graphql instance + ## How to force pull images from the registry -By default, most images (heart-monitor, liquidator, etc.) are cached locally and won't re-fetch from upstream registries. To force a pull, you can run +By default, most images (heartmonitor, etc.) are cached locally and won't re-fetch from upstream registries. To force a pull, you can run ```sh make chaosnet-build diff --git a/contrib/docker-compose/README.md b/contrib/docker-compose/README.md index f588d5728..e888d4b90 100644 --- a/contrib/docker-compose/README.md +++ b/contrib/docker-compose/README.md @@ -1,13 +1,18 @@ # contrib/docker-compose -- [docker-compose-chaosnet](#docker-compose-chaosnet) - - [Usage Commands:](#usage-commands) - - [Nibiru node services](#nibiru-node-services) - - [Oracle feeder services](#oracle-feeder-services) - - [Hermes IBC relayer services](#hermes-ibc-relayer-services) - - [Faucet Service](#faucet-service) - - [Heart Monitor Services](#heart-monitor-services) -- [Reference Materials](#reference-materials) +- [contrib/docker-compose](#contribdocker-compose) + - [docker-compose-chaosnet](#docker-compose-chaosnet) + - [Usage](#usage) + - [Single validator node + pricefeeder](#single-validator-node--pricefeeder) + - [Two validator nodes + pricefeeder + IBC relayer](#two-validator-nodes--pricefeeder--ibc-relayer) + - [Single validator node + heartmonitor](#single-validator-node--heartmonitor) + - [Other Commands](#other-commands) + - [Services Overview](#services-overview) + - [Nibiru node services](#nibiru-node-services) + - [Pricefeeder services](#pricefeeder-services) + - [Hermes IBC relayer services](#hermes-ibc-relayer-services) + - [Heart Monitor Services](#heart-monitor-services) + - [Reference Materials](#reference-materials) ## docker-compose-chaosnet @@ -18,17 +23,38 @@ Nibiru-specific containers. Features: -- Data volume mounts ensure persistent storage. - Different ports are utilized to mimic a multi-chain configuration on a single machine. -- Enables testing of cross-chain transactions, chain health monitoring, liquidations, and more in a local Docker context across two chains. +- Enables testing of cross-chain transactions, chain health monitoring, and more in a local Docker context across two chains. -### Usage Commands: +## Usage + +### Single validator node + pricefeeder + +```sh +docker compose -f docker-compose-chaosnet.yml up +``` + +### Two validator nodes + pricefeeder + IBC relayer + +```sh +docker compose -f docker-compose-chaosnet.yml --profile ibc up +``` + +### Single validator node + heartmonitor + +```sh +docker compose -f docker-compose-chaosnet.yml --profile heartmonitor up +``` + +### Other Commands - `docker compose up`: Start the services. - `docker compose down`: Stop the services. - `docker compose restart`: Restart all services. - `docker compose ps`: List containers, their status, ports, etc. -- `docker compose logs`: View std output from containers +- `docker compose logs`: View std output from containers + +## Services Overview ### Nibiru node services @@ -36,7 +62,7 @@ Features: running on different ports, using unique mnemonics and chain IDs, imitating two independent blockchain networks. -### Oracle feeder services +### Pricefeeder services - `pricefeeder-0` and `pricefeeder-1` (Service): Two price feeder services that push price data to the respective Nibiru nodes. @@ -44,6 +70,7 @@ Features: ### Hermes IBC relayer services An IBC relayer is set up to connect the two chains using [hermes](https://hermes.informal.systems/). + 1. `hermes-keys-task-0` and `hermes-keys-task-1` (Service): Tasks to generate keys for the validators on `nibiru-0` and `nibiru-1`. 2. `hermes-client-connection-channel-task` (Service): Creates a new channel @@ -59,34 +86,24 @@ Brief IBC reference: Put simply, **connections** represent a secure communication line between two blockchain to transfer IBC **packets** (data). Once a connection is established, light client of two chains, usually called the source chain and destination -chain, is established. +chain, is established. Once a connection is established, **channels** can be formed. A channel represents a logical pathway for specific types communication over the connection (like token transfers and other relaying of IBC packets. -### Faucet Service - -Dispenses testnet tokens. Faucets provide liquidity on test chains. - -Repository: [NibiruChain/go-faucet](https://github.com/NibiruChain/go-faucet) - ### Heart Monitor Services - `heartmonitor-db`: A postgres database for the heart monitor. - `heartmonitor`: An indexing solution that populates a DB based on events and - block responses emitted from Nibiru nodes. - -- `liquidator` : Liquidates underwater positions using the tracked state inside - `hearmonitor-db`. + block responses emitted from Nibiru nodes. - `graphql`: GraphQL API for the heart monitor data. Used in the Nibiru web app and other off-chain tools. Repository: [NibiruChain/go-heartmonitor](https://github.com/NibiruChain/go-heartmonitor). - ## Reference Materials - [Docker Compose file](https://docs.docker.com/compose/compose-file/03-compose-file/) diff --git a/contrib/docker-compose/docker-compose-chaosnet.yml b/contrib/docker-compose/docker-compose-chaosnet.yml index 35708aee0..df9514b01 100644 --- a/contrib/docker-compose/docker-compose-chaosnet.yml +++ b/contrib/docker-compose/docker-compose-chaosnet.yml @@ -48,6 +48,7 @@ services: RPC_PORT: 36657 GRPC_PORT: 19090 LCD_PORT: 11317 + profiles: [ibc] healthcheck: test: ["CMD", "curl", "-f", "http://localhost:36657"] interval: 60s @@ -63,6 +64,7 @@ services: pricefeeder-1: image: ghcr.io/nibiruchain/pricefeeder:latest + profiles: [ibc] restart: always environment: CHAIN_ID: nibiru-localnet-1 @@ -77,6 +79,7 @@ services: hermes-keys-task-0: image: informalsystems/hermes:1.6.0 + profiles: [ibc] user: root command: [ @@ -107,6 +110,7 @@ services: hermes-keys-task-1: image: informalsystems/hermes:1.6.0 + profiles: [ibc] user: root command: [ @@ -139,6 +143,7 @@ services: hermes-client-connection-channel-task: image: informalsystems/hermes:1.6.0 + profiles: [ibc] user: root command: [ @@ -172,6 +177,7 @@ services: hermes: image: informalsystems/hermes:1.6.0 + profiles: [ibc] restart: always user: root command: ["start"] @@ -191,22 +197,9 @@ services: hermes-client-connection-channel-task: condition: service_completed_successfully - faucet: - restart: always - image: ghcr.io/nibiruchain/go-faucet:latest - environment: - NODE: nibiru-0:9090 - MNEMONIC: undo donkey arena rule old portion long forget rescue post stuff normal reduce raw unable warrior method stairs valley enhance glory lens sign zero - SEND_COINS: 11000000unibi - MAX_SEND_COINS: 110000000unibi - depends_on: - nibiru-0: - condition: service_healthy - ports: - - 8000:8000 - heartmonitor-db: image: postgres:14 + profiles: [heartmonitor] restart: always environment: POSTGRES_PASSWORD: postgres @@ -216,6 +209,8 @@ services: heartmonitor: image: ghcr.io/nibiruchain/go-heartmonitor:latest + platform: linux/amd64 + profiles: [heartmonitor] restart: always command: --clean volumes: @@ -231,26 +226,10 @@ services: heartmonitor-db: condition: service_started - liquidator: - image: ghcr.io/nibiruchain/go-heartmonitor:latest - restart: always - command: --liquidator - environment: - - DATABASE_URI=postgresql://postgres:postgres@heartmonitor-db:5432/heart-monitor?sslmode=disable - - TENDERMINT_RPC_ENDPOINT=http://nibiru-0:26657 - - GRPC_ENDPOINT=tcp://nibiru-0:9090 - - GRPC_INSECURE=true - - NO_PARTITIONS=true - - LIQUIDATOR_MNEMONIC=record damage person caution truly riot resource luxury rude guide mushroom athlete fantasy dentist friend mule depth salmon photo unfold exclude coyote idea evoke - - LIQUIDATOR_GAS_LIMIT_INITIAL=500000 - - LIQUIDATOR_GAS_MULTIPLIER=5 - - LIQUIDATOR_GAS_MAX_ATTEMPTS=10 - depends_on: - heartmonitor: - condition: service_started - graphql: image: ghcr.io/nibiruchain/go-heartmonitor:latest + platform: linux/amd64 + profiles: [heartmonitor] restart: always command: --graphql environment: diff --git a/contrib/docker-compose/docker-compose-multi-node.yml b/contrib/docker-compose/docker-compose-multi-node.yml deleted file mode 100644 index e5e5b986e..000000000 --- a/contrib/docker-compose/docker-compose-multi-node.yml +++ /dev/null @@ -1,64 +0,0 @@ -version: '3' - -services: - node0: - container_name: node0 - image: "nibiru/node" - ports: - - "26656-26657:26656-26657" - - "9090:9090" - - "1317:1317" - environment: - - ID=0 - volumes: - - ../../data/node0/nibid:/root/.nibid:Z - networks: - localnet: - ipv4_address: 192.168.11.2 - - node1: - container_name: node1 - image: "nibiru/node" - ports: - - "26659-26660:26656-26657" - environment: - - ID=1 - volumes: - - ../../data/node1/nibid:/root/.nibid:Z - networks: - localnet: - ipv4_address: 192.168.11.3 - - node2: - container_name: node2 - image: "nibiru/node" - environment: - - ID=2 - ports: - - "26661-26662:26656-26657" - volumes: - - ../../data/node2/nibid:/root/.nibid:Z - networks: - localnet: - ipv4_address: 192.168.11.4 - - node3: - container_name: node3 - image: "nibiru/node" - environment: - - ID=3 - ports: - - "26663-26664:26656-26657" - volumes: - - ../../data/node3/nibid:/root/.nibid:Z - networks: - localnet: - ipv4_address: 192.168.11.5 - -networks: - localnet: - driver: bridge - ipam: - driver: default - config: - - subnet: 192.168.11.0/16 diff --git a/contrib/make/chaosnet.mk b/contrib/make/chaosnet.mk index df46d516b..6bcc88ef4 100644 --- a/contrib/make/chaosnet.mk +++ b/contrib/make/chaosnet.mk @@ -12,10 +12,22 @@ chaosnet-build: chaosnet: chaosnet-down docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml up --detach --build -# Stop chaosnet +# Run a chaosnet testnet locally with IBC enabled +.PHONY: chaosnet-ibc +chaosnet-ibc: chaosnet-down + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml --profile ibc up --detach --build + +# Run a chaosnet testnet locally with heartmonitor+graphql stack enabled +.PHONY: chaosnet-heartmonitor +chaosnet-heartmonitor: chaosnet-down + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml --profile heartmonitor up --detach --build + +# Stop chaosnet, need to specify all profiles to ensure all services are stopped +# See https://stackoverflow.com/questions/76781634/docker-compose-down-all-profiles .PHONY: chaosnet-down chaosnet-down: - docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml down --timeout 1 --volumes + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml --profile ibc down --timeout 1 --volumes + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml --profile heartmonitor down --timeout 1 --volumes ############################################################################### ### Chaosnet Logs ### @@ -25,10 +37,6 @@ chaosnet-down: chaosnet-logs: docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml logs -.PHONY: chaosnet-logs-faucet -chaosnet-logs-faucet: - docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml logs go-faucet - .PHONY: chaosnet-logs-pricefeeder chaosnet-logs-pricefeeder: docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml logs pricefeeder --follow @@ -41,9 +49,13 @@ chaosnet-logs-go-heartmonitor: ### Chaosnet SSH ### ############################################################################### -.PHONY: chaosnet-ssh-nibiru -chaosnet-ssh-nibiru: - docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml exec -it nibiru /bin/sh +.PHONY: chaosnet-ssh-nibiru-0 +chaosnet-ssh-nibiru-0: + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml exec -it nibiru-0 /bin/sh + +.PHONY: chaosnet-ssh-nibiru-1 +chaosnet-ssh-nibiru-1: + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml exec -it nibiru-1 /bin/sh .PHONY: chaosnet-ssh-go-heartmonitor chaosnet-ssh-go-heartmonitor: diff --git a/contrib/scripts/chaosnet.sh b/contrib/scripts/chaosnet.sh index 40faf61e6..ffee52fd8 100755 --- a/contrib/scripts/chaosnet.sh +++ b/contrib/scripts/chaosnet.sh @@ -25,6 +25,7 @@ sed -i 's/log_format = .*/log_format = "json"/' $HOME/.nibid/config/config.toml sed -i '/\[api\]/,+3 s/enable = false/enable = true/' $HOME/.nibid/config/app.toml sed -i 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/' $HOME/.nibid/config/app.toml sed -i "s/localhost:1317/0.0.0.0:$LCD_PORT/" $HOME/.nibid/config/app.toml +sed -i '/\[grpc\]/,+3 s/enable = false/enable = true/' $HOME/.nibid/config/app.toml sed -i "s/localhost:9090/0.0.0.0:$GRPC_PORT/" $HOME/.nibid/config/app.toml # ------------------------------------------------------------------------ From 8db3e89d521bfab5ebb07aa1644cfb22eb1d585e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 03:36:16 +0000 Subject: [PATCH 2/3] chore(deps): bump github.com/hashicorp/go-getter from 1.7.4 to 1.7.5 (#1938) * chore(deps): bump github.com/hashicorp/go-getter from 1.7.4 to 1.7.5 Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.7.4 to 1.7.5. - [Release notes](https://github.com/hashicorp/go-getter/releases) - [Changelog](https://github.com/hashicorp/go-getter/blob/main/.goreleaser.yml) - [Commits](https://github.com/hashicorp/go-getter/compare/v1.7.4...v1.7.5) --- updated-dependencies: - dependency-name: github.com/hashicorp/go-getter dependency-type: indirect ... Signed-off-by: dependabot[bot] * Updated changelog - dependabot --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Unique-Divine Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> --- CHANGELOG.md | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a9986ed3..4da43a122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,7 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `golang.org/x/net` from 0.20.0 to 0.23.0 ([#1850](https://github.com/NibiruChain/nibiru/pull/1850)) - Bump `github.com/supranational/blst` from 0.3.8-0.20220526154634-513d2456b344 to 0.3.11 ([#1851](https://github.com/NibiruChain/nibiru/pull/1851)) - Bump `golangci/golangci-lint-action` from 4 to 6 ([#1854](https://github.com/NibiruChain/nibiru/pull/1854), [#1867](https://github.com/NibiruChain/nibiru/pull/1867)) -- Bump `github.com/hashicorp/go-getter` from 1.7.1 to 1.7.4 ([#1858](https://github.com/NibiruChain/nibiru/pull/1858)) +- Bump `github.com/hashicorp/go-getter` from 1.7.1 to 1.7.5 ([#1858](https://github.com/NibiruChain/nibiru/pull/1858), [#1938](https://github.com/NibiruChain/nibiru/pull/1938)) - Bump `github.com/btcsuite/btcd` from 0.23.3 to 0.24.0 ([#1862](https://github.com/NibiruChain/nibiru/pull/1862)) - Bump `pozetroninc/github-action-get-latest-release` from 0.7.0 to 0.8.0 ([#1863](https://github.com/NibiruChain/nibiru/pull/1863)) - Bump `bufbuild/buf-setup-action` from 1.30.1 to 1.33.0 ([#1891](https://github.com/NibiruChain/nibiru/pull/1891), [#1900](https://github.com/NibiruChain/nibiru/pull/1900), [#1923](https://github.com/NibiruChain/nibiru/pull/1923)) diff --git a/go.mod b/go.mod index 110e426fb..2984a00cc 100644 --- a/go.mod +++ b/go.mod @@ -149,7 +149,7 @@ 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.4 // 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 diff --git a/go.sum b/go.sum index d3a16f130..f60663cd1 100644 --- a/go.sum +++ b/go.sum @@ -780,8 +780,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.4 h1:3yQjWuxICvSpYwqSayAdKRFcvBl1y/vogCxczWSmix0= -github.com/hashicorp/go-getter v1.7.4/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= From 422c67652d7c26e410ba99aa448c4dfd7dd7f71c Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Tue, 25 Jun 2024 23:37:37 -0500 Subject: [PATCH 3/3] feat(evm): EVM fungible token protobufs and encoding tests (#1936) * refactor(e2e-evm): unused vars and better type hint Squashed commit of the following: commit 51071dad60c12e02093d92d1311d33b70b81304f Author: Unique-Divine Date: Mon Jun 10 12:32:00 2024 -0500 refactor(e2e-evm): unused vars and better type hint commit 6729b97643395ed11f4cec15f7885dc0c6e88e56 Author: Unique-Divine Date: Fri Jun 7 22:46:14 2024 -0500 test(e2e-evm): more type safety commit 25c1557dda67fa19127f83d1dcdc2c38116fbde7 Author: Unique-Divine Date: Fri Jun 7 22:01:21 2024 -0500 Squashed commit of the following: commit 021d161d176112cf24e28780ad64f61155f70ea2 Author: Unique-Divine Date: Fri Jun 7 22:00:10 2024 -0500 test(e2e): (1) Generated smart contract types for ethers. (2) TypeScript support. (3) Formatter commit af7e7b30908a0e189c9435ed29ecf79b98f4e31c Author: Unique-Divine Date: Fri Jun 7 16:11:45 2024 -0500 chore: another issue ticket commit 36745fde9d0228252e961f64d728ad8e51ff1ca6 Author: Unique-Divine Date: Fri Jun 7 16:07:20 2024 -0500 chore: add issue number for TODO comment commit 8a76c0eded70859254fcefea6982550c2e2a3bc5 Author: Unique-Divine Date: Fri Jun 7 15:54:07 2024 -0500 refactor(evm): Remove dead code and document non-EVM ante handler commit e4e11dfd6ad075620683cb703125f29b9d1b574a Author: Unique-Divine Date: Fri Jun 7 15:52:38 2024 -0500 refactor: remove dead code commit cad00c03648a01ac678b248751624f35f909cc83 Merge: dc5f4dd8 359e310f Author: Unique-Divine Date: Fri Jun 7 15:41:53 2024 -0500 Merge branch 'main' into ud/ante-test commit dc5f4dd84d9049dc3069fc6f0e65936b04f7dbce Author: Unique-Divine Date: Fri Jun 7 15:28:53 2024 -0500 refactor: ante handler and evm cleanup commit f73cdc3e75318e0a66f6f77cb117984bc09c90f1 Merge: d3a6ea95 290c3724 Author: Unique-Divine Date: Wed Jun 5 20:59:39 2024 -0500 Merge branch 'test/evm-grpc-query' of https://github.com/NibiruChain/nibiru into test/evm-grpc-query commit d3a6ea956f71d3042e0ef14ee149d502c3ccbbdf Merge: 376596d6 70ee1bfe Author: Unique-Divine Date: Wed Jun 5 20:59:30 2024 -0500 Merge branch 'main' into test/evm-grpc-query commit 376596d61b44856cf0866486f7984d561ffe820c Author: Unique-Divine Date: Wed Jun 5 20:58:40 2024 -0500 Squashed commit of the following: commit b5687130ff5f3d020a3b14d219fec3a816579c30 Author: Unique-Divine Date: Wed Jun 5 20:57:44 2024 -0500 chore: run tidy commit 1f1f9385952c4a170f744726bed8a3ee7c376028 Merge: 3e3cc837 bbcc6f8c Author: Unique-Divine Date: Wed Jun 5 19:16:30 2024 -0500 Merge branch 'main' into ud/fix-race-condition commit 3e3cc837b204971c58c775fe25d28fd01bce4021 Author: Unique-Divine Date: Wed Jun 5 19:15:40 2024 -0500 chore: changelog commit 3876ccb431aac5c9991a3540d764061cb52a0857 Author: Unique-Divine Date: Wed Jun 5 19:04:00 2024 -0500 refactor: more consistent test names commit aaa0a19f103a12c60f226a5057779a74d680e61c Author: Unique-Divine Date: Wed Jun 5 18:53:09 2024 -0500 test(oracle): Fix missing tear down step for oracle integration test commit 8c3c35eafc41d29becba1379d1f9ca5e984d8d9a Author: Unique-Divine Date: Wed Jun 5 17:55:56 2024 -0500 chore: add test comands to justfile commit 4916282353300b2dbf639e599cfbc3685cda01f6 Merge: 64ed0a29 e7e708d7 Author: Unique-Divine Date: Fri May 31 09:35:33 2024 -0500 Merge branch 'main' into ud/fix-race-condition commit 64ed0a29c918c4c1402eceddc13998ed4a156712 Author: Unique-Divine Date: Fri May 31 01:44:55 2024 -0500 fix(gosdk): tests parallel race condition commit 290c3724c2fc3804e5b2c8b509a2707c2e9ea101 Merge: 0d1c8942 70ee1bfe Author: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Wed Jun 5 20:05:19 2024 -0500 Merge branch 'main' into test/evm-grpc-query commit 0d1c8942c6453d2f2fa22dff0e5af8ea11e1fa53 Merge: 91708355 ad173e9b Author: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Wed Jun 5 19:34:38 2024 -0500 Merge branch 'main' into test/evm-grpc-query commit 917083558b04e67f563dd0f12c4918e2b43ecf7c Author: Oleg Nikonychev Date: Wed Jun 5 13:55:14 2024 +0400 fix: removed hardcoded gas value in grpc_query test commit 43378584dfec2f207afdc619f2d27df35816c304 Author: Oleg Nikonychev Date: Wed Jun 5 13:14:34 2024 +0400 chore: refactored eth util methods commit 7df84e26d481013d30f67710e69dcaca02affcfa Merge: 8918498b bbcc6f8c Author: Oleg Nikonychev Date: Wed Jun 5 12:23:51 2024 +0400 chore: resolve conflicts commit 8918498b83925f1b70678ecf70250906b9a3038d Merge: 3fd45ce3 e7e708d7 Author: Oleg Nikonychev Date: Mon Jun 3 21:56:39 2024 +0400 Merge branch 'main' into test/evm-grpc-query commit 3fd45ce323c97f999d6754ef454e51077854a918 Author: Oleg Nikonychev Date: Mon Jun 3 21:56:23 2024 +0400 chore: changelog update commit 3348876f46c66c9502d16793bb61b729559ecc91 Author: Oleg Nikonychev Date: Mon Jun 3 21:53:54 2024 +0400 test(evm): grpc_query full coverage * wip!: save progress * chore: changelog * chore: linter * feat(eth): finish todos for hex.go * feat(eth): finalize protobuf impl for eth.HexAddr * chore: small typos in comments --------- Co-authored-by: Kevin Yang <5478483+k-yang@users.noreply.github.com> --- CHANGELOG.md | 1 + e2e/evm/test/contract_send_nibi.test.ts | 7 +- eth/hex.go | 128 +++++++ eth/hex_test.go | 272 +++++++++++++++ eth/state_encoder.go | 12 +- proto/eth/evm/v1/evm.proto | 20 ++ proto/eth/evm/v1/genesis.proto | 4 + x/evm/access_list_test.go | 1 + x/evm/const.go | 7 + x/evm/events.go | 18 +- x/evm/evm.go | 57 ++++ x/evm/evm.pb.go | 421 +++++++++++++++++++----- x/evm/evm_test.go | 112 +++++++ x/evm/genesis.pb.go | 105 ++++-- x/evm/keeper/evm_state.go | 2 +- x/evm/keeper/funtoken_state.go | 89 +++++ x/evm/keeper/keeper.go | 7 + x/evm/query_test.go | 58 ---- 18 files changed, 1147 insertions(+), 174 deletions(-) create mode 100644 eth/hex.go create mode 100644 eth/hex_test.go create mode 100644 x/evm/evm.go create mode 100644 x/evm/evm_test.go create mode 100644 x/evm/keeper/funtoken_state.go delete mode 100644 x/evm/query_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4da43a122..5694d65ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1914](https://github.com/NibiruChain/nibiru/pull/1914) - refactor(evm): Remove dead code and document non-EVM ante handler- [#1917](https://github.com/NibiruChain/nibiru/pull/1917) - test(e2e-evm): TypeScript support. Type generation from compiled contracts. Formatter for TS code. - [#1917](https://github.com/NibiruChain/nibiru/pull/1917) - test(e2e-evm): TypeScript support. Type generation from compiled contracts. Formatter for TS code. - [#1922](https://github.com/NibiruChain/nibiru/pull/1922) - feat(evm): tracer option is read from the config. +- [#1936](https://github.com/NibiruChain/nibiru/pull/1936) - feat(evm): EVM fungible token protobufs and encoding tests #### Dapp modules: perp, spot, oracle, etc diff --git a/e2e/evm/test/contract_send_nibi.test.ts b/e2e/evm/test/contract_send_nibi.test.ts index 75d368846..bbbe78b88 100644 --- a/e2e/evm/test/contract_send_nibi.test.ts +++ b/e2e/evm/test/contract_send_nibi.test.ts @@ -1,10 +1,9 @@ -import { describe, it, expect, beforeAll } from "bun:test" // eslint-disable-line import/no-unresolved -import { AddressLike, ethers } from "ethers" +import { describe, it, expect } from "bun:test" // eslint-disable-line import/no-unresolved +import { ethers } from "ethers" import { account, provider, deployContract } from "./setup" import { SendNibiCompiled } from "../types/ethers-contracts" -import { TypedContractMethod } from "../types/ethers-contracts/common" -type SendMethod = TypedContractMethod<[_to: AddressLike], [void], "payable"> +type SendMethod = SendNibiCompiled["sendViaCall"] const doContractSend = async (sendMethod: SendMethod) => { const recipientAddress = ethers.Wallet.createRandom().address diff --git a/eth/hex.go b/eth/hex.go new file mode 100644 index 000000000..3e6cf0cde --- /dev/null +++ b/eth/hex.go @@ -0,0 +1,128 @@ +package eth + +import ( + "encoding/json" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" +) + +/////////// HexAddr + +// HexAddr: An ERC55-compliant hexadecimal-encoded string representing the 20 +// byte address of an Ethereum account. +type HexAddr string + +var _ sdk.CustomProtobufType = (*HexAddr)(nil) + +func NewHexAddr(addr EthAddr) HexAddr { + return HexAddr(addr.Hex()) +} + +func NewHexAddrFromStr(addr string) (HexAddr, error) { + hexAddr := HexAddr(gethcommon.HexToAddress(addr).Hex()) + if !gethcommon.IsHexAddress(addr) { + return hexAddr, fmt.Errorf( + "%s: input \"%s\" is not an ERC55-compliant, 20 byte hex address", + HexAddrError, addr, + ) + } + return hexAddr, hexAddr.Valid() +} + +// MustNewHexAddrFromStr is the same as [NewHexAddrFromStr], except it panics +// when there's an error. +func MustNewHexAddrFromStr(addr string) HexAddr { + hexAddr, err := NewHexAddrFromStr(addr) + if err != nil { + panic(err) + } + return hexAddr +} + +const HexAddrError = "HexAddrError" + +func (h HexAddr) Valid() error { + // Check address encoding bijectivity + wantAddr := h.ToAddr().Hex() // gethcommon.Address.Hex() + haveAddr := string(h) // should be equivalent to ↑ + + if !gethcommon.IsHexAddress(haveAddr) || haveAddr != wantAddr { + return fmt.Errorf( + "%s: Ethereum address is not represented as expected. We have encoding \"%s\" and instead need \"%s\" (gethcommon.Address.Hex)", + HexAddrError, haveAddr, wantAddr, + ) + } + + return nil +} + +func (h HexAddr) ToAddr() EthAddr { + return gethcommon.HexToAddress(string(h)) +} + +// ToBytes gets the string representation of the underlying address. +func (h HexAddr) ToBytes() []byte { + return h.ToAddr().Bytes() +} + +func (h HexAddr) String() string { return h.ToAddr().Hex() } + +// Marshal implements the gogo proto custom type interface. +// Ref: https://github.com/cosmos/gogoproto/blob/v1.5.0/custom_types.md +func (h HexAddr) Marshal() ([]byte, error) { + return []byte(h), nil +} + +// MarshalJSON returns the [HexAddr] as JSON bytes. +// Implements the gogo proto custom type interface. +// Ref: https://github.com/cosmos/gogoproto/blob/v1.5.0/custom_types.md +func (h HexAddr) MarshalJSON() ([]byte, error) { + return []byte("\"" + h.String() + "\""), nil // a string is already JSON +} + +// MarshalTo serializes a pre-allocated byte slice ("data") directly into the +// [HexAddr] value, avoiding unnecessary memory allocations. +// MarshalTo implements the gogo proto custom type interface. +// Implements the gogo proto custom type interface. +// Ref: https://github.com/cosmos/gogoproto/blob/v1.5.0/custom_types.md +func (h *HexAddr) MarshalTo(data []byte) (n int, err error) { + bz := []byte{} + copy(data, bz) + hexAddr, err := NewHexAddrFromStr(string(bz)) + *h = hexAddr + return h.Size(), err +} + +// Unmarshal implements the gogo proto custom type interface. +// Ref: https://github.com/cosmos/gogoproto/blob/v1.5.0/custom_types.md +func (h *HexAddr) Unmarshal(data []byte) error { + hexAddr, err := NewHexAddrFromStr(string(data)) + *h = hexAddr + return err +} + +// UnmarshalJSON implements the gogo proto custom type interface. +// Ref: https://github.com/cosmos/gogoproto/blob/v1.5.0/custom_types.md +func (h *HexAddr) UnmarshalJSON(bz []byte) error { + text := new(string) + if err := json.Unmarshal(bz, text); err != nil { + return err + } + + hexAddr, err := NewHexAddrFromStr(*text) + if err != nil { + return err + } + + *h = hexAddr + + return nil +} + +// Size implements the gogo proto custom type interface. +// Ref: https://github.com/cosmos/gogoproto/blob/v1.5.0/custom_types.md +func (h HexAddr) Size() int { + return len(h) +} diff --git a/eth/hex_test.go b/eth/hex_test.go new file mode 100644 index 000000000..8d11fafce --- /dev/null +++ b/eth/hex_test.go @@ -0,0 +1,272 @@ +package eth_test + +import ( + "fmt" + "strconv" + "strings" + + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/common/set" +) + +var threeValidAddrs []eth.HexAddr = []eth.HexAddr{ + eth.MustNewHexAddrFromStr("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"), + eth.MustNewHexAddrFromStr("0xAe967917c465db8578ca9024c205720b1a3651A9"), + eth.MustNewHexAddrFromStr("0x1111111111111111111112222222222223333323"), +} + +func (s *Suite) TestHexAddr_UniqueMapping() { + type CorrectAnswer struct { + gethAddrOut gethcommon.Address + hexAddrOut eth.HexAddr + } + + for tcIdx, tc := range []struct { + equivSet set.Set[string] + }{ + { + equivSet: set.New( + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + "0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED", + "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + "0X5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED", + ), + }, + } { + s.Run(strconv.Itoa(tcIdx), func() { + s.T().Log("Show that each member of the set is equivalent") + var answer CorrectAnswer + for idx, equivHexAddrString := range tc.equivSet.ToSlice() { + gethAddrOut := gethcommon.HexToAddress(equivHexAddrString) + hexAddrOut, err := eth.NewHexAddrFromStr(equivHexAddrString) + s.NoError(err) + if idx == 0 { + answer = CorrectAnswer{ + gethAddrOut: gethAddrOut, + hexAddrOut: hexAddrOut, + } + continue + } + + s.Equal(answer.gethAddrOut, gethAddrOut) + s.Equal(answer.gethAddrOut, hexAddrOut.ToAddr()) + s.Equal(answer.hexAddrOut, hexAddrOut) + } + }) + } +} + +// TestHexAddr_NewHexAddr: Test to showcase the flexibility of inputs that can be +// passed to `eth.NewHexAddrFromStr` and result in a "valid" `HexAddr` that preserves +// bijectivity with `gethcommon.Address` and has a canonical string +// representation. +// +// We only want to store valid `HexAddr` strings in state. Hex addresses that +// include or remove the prefix, or change the letters to and from lower and +// upper case will all produce the same `HexAddr` when passed to +// `eth.NewHexAddrFromStr`. +func (s *Suite) TestHexAddr_NewHexAddr() { + // InputAddrVariation: An instance of a "hexAddr" that derives to the + // expected Ethereum address and results in the same string representation. + type InputAddrVariation struct { + hexAddr string + testCaseName string + wantNotEqual bool + } + + for _, tcGroup := range []struct { + want eth.HexAddr + hexAddrs []InputAddrVariation + }{ + { + want: threeValidAddrs[0], + hexAddrs: []InputAddrVariation{ + { + hexAddr: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + testCaseName: "happy: no-op (sanity check to show constructor doesn't break a valid input)", + }, + { + hexAddr: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + testCaseName: "happy: lower case is valid", + }, + { + hexAddr: "0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED", + testCaseName: "happy: upper case is valid", + }, + { + hexAddr: "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + testCaseName: "happy: 0x prefix: missing", + }, + { + hexAddr: "nibi1zaa12312312aacbcbeabea123", + testCaseName: "sad: bech32 is not hex", + wantNotEqual: true, + }, + }, + }, + { + want: threeValidAddrs[1], + hexAddrs: []InputAddrVariation{ + { + hexAddr: "0XAe967917c465db8578ca9024c205720b1a3651A9", + testCaseName: "happy: 0x prefix: typo", + }, + { + hexAddr: "0xae967917c465db8578ca9024c205720b1a3651A9", + testCaseName: "happy: mixed case checksum not valid according to ERC55", + }, + { + hexAddr: "not-a-hex-addr", + testCaseName: "sad: sanity check: clearly not a hex addr", + wantNotEqual: true, + }, + }, + }, + { + want: threeValidAddrs[2], + hexAddrs: []InputAddrVariation{ + { + hexAddr: "0x1111111111111111111112222222222223333323", + testCaseName: "happy", + }, + }, + }, + } { + want := tcGroup.want + for _, tc := range tcGroup.hexAddrs { + tcName := fmt.Sprintf("want %s, %s", want, tc.testCaseName) + s.Run(tcName, func() { + got, err := eth.NewHexAddrFromStr(tc.hexAddr) + + // gethcommon.Address input should give the same thing + got2 := eth.NewHexAddr(gethcommon.HexToAddress(tc.hexAddr)) + if tc.wantNotEqual { + s.NotEqual(want, got) + s.NotEqual(want.ToAddr(), got.ToAddr()) + s.NotEqual(want, got2) + s.NotEqual(want.ToAddr(), got2.ToAddr()) + s.Require().Error(err) + return + } else { + // string input should give the canonical HexAddr + s.Equal(want, got) + s.Equal(want.ToAddr(), got.ToAddr()) + + // gethcommon.Address input should give the same thing + got2 := eth.NewHexAddr(gethcommon.HexToAddress(tc.hexAddr)) + s.Equal(want, got2) + s.Equal(want.ToAddr(), got2.ToAddr()) + } + + s.Require().NoError(err) + }) + } + } +} + +// TestHexAddr_Valid: Test that demonstrates +func (s *Suite) TestHexAddr_Valid() { + for _, tc := range []struct { + name string + hexAddr string + wantErr string + }{ + { + name: "happy 0", + hexAddr: threeValidAddrs[0].String(), + }, + { + name: "happy 1", + hexAddr: threeValidAddrs[1].String(), + }, + { + name: "happy 2", + hexAddr: threeValidAddrs[2].String(), + }, + { + name: "0x prefix: missing", + hexAddr: "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + wantErr: eth.HexAddrError, + }, + { + name: "0x prefix: typo", + hexAddr: "0XAe967917c465db8578ca9024c205720b1a3651A9", + wantErr: eth.HexAddrError, + }, + { + name: "mixed case checksum not valid according to ERC55", + hexAddr: "0xae967917c465db8578ca9024c205720b1a3651A9", + wantErr: eth.HexAddrError, + }, + { + name: "sad 1", + hexAddr: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + wantErr: eth.HexAddrError, + }, + } { + s.Run(tc.name, func() { + err := eth.HexAddr(tc.hexAddr).Valid() + if tc.wantErr != "" { + s.Require().ErrorContains(err, tc.wantErr) + return + } + s.Require().NoError(err) + }) + } +} + +func withQuotes(s string) string { return fmt.Sprintf("\"%s\"", s) } + +func withoutQuotes(s string) string { + return strings.TrimPrefix(strings.TrimSuffix(s, "\""), "\"") +} + +func (s *Suite) TestProtobufEncoding() { + for tcIdx, tc := range []struct { + given eth.HexAddr + json string + wantErr string + }{ + { + given: threeValidAddrs[0], + json: withQuotes(threeValidAddrs[0].String()), + }, + { + given: threeValidAddrs[1], + json: withQuotes(threeValidAddrs[1].String()), + }, + { + given: threeValidAddrs[2], + json: withQuotes(threeValidAddrs[2].String()), + }, + } { + s.Run(strconv.Itoa(tcIdx), func() { + givenMut := tc.given + jsonBz, err := givenMut.MarshalJSON() + s.NoError(err) + s.Equal(tc.json, string(jsonBz)) + + err = (&givenMut).UnmarshalJSON(jsonBz) + s.NoError(err) + s.Equal(givenMut, tc.given, + "Given -> MarshalJSON -> UnmarshalJSON returns a different value than the given when it should be an identity operation (no-op). test case #%d", tcIdx) + + bz, err := tc.given.Marshal() + s.NoError(err) + jsonBzWithoutQuotes := withoutQuotes(tc.json) + s.Equal(jsonBzWithoutQuotes, string(bz), + "Marshaling to bytes gives different value than the test case specifies. test case #%d", tcIdx) + + err = (&givenMut).Unmarshal(bz) + s.NoError(err) + s.Equal(tc.given, givenMut, + "Given -> Marshal -> Unmarshal returns a different value than the given when it should be an identity operation (no-op). test case #%d", tcIdx) + + s.Equal(len(tc.given), tc.given.Size()) + s.Equal(len(tc.json), tc.given.Size()+2) + }) + } +} diff --git a/eth/state_encoder.go b/eth/state_encoder.go index 4c329b029..205ab7097 100644 --- a/eth/state_encoder.go +++ b/eth/state_encoder.go @@ -4,7 +4,7 @@ import ( fmt "fmt" "github.com/NibiruChain/collections" - ethcommon "github.com/ethereum/go-ethereum/common" + gethcommon "github.com/ethereum/go-ethereum/common" ) // BytesToHex converts a byte array to a hexadecimal string @@ -13,10 +13,10 @@ func BytesToHex(bz []byte) string { } // EthAddr: (alias) 20 byte address of an Ethereum account. -type EthAddr = ethcommon.Address +type EthAddr = gethcommon.Address // EthHash: (alias) 32 byte Keccak256 hash of arbitrary data. -type EthHash = ethcommon.Hash //revive:disable-line:exported +type EthHash = gethcommon.Hash //revive:disable-line:exported var ( // Implements a `collections.ValueEncoder` for the `[]byte` type @@ -44,7 +44,7 @@ func (_ veBytes) Name() string { return "[]byte" } type veEthAddr struct{} func (_ veEthAddr) Encode(value EthAddr) []byte { return value.Bytes() } -func (_ veEthAddr) Decode(bz []byte) EthAddr { return ethcommon.BytesToAddress(bz) } +func (_ veEthAddr) Decode(bz []byte) EthAddr { return gethcommon.BytesToAddress(bz) } func (_ veEthAddr) Stringify(value EthAddr) string { return value.Hex() } func (_ veEthAddr) Name() string { return "EthAddr" } @@ -65,7 +65,7 @@ type keEthAddr struct{} func (_ keEthAddr) Encode(value EthAddr) []byte { return value.Bytes() } func (_ keEthAddr) Decode(bz []byte) (int, EthAddr) { - return ethcommon.AddressLength, ethcommon.BytesToAddress(bz) + return gethcommon.AddressLength, gethcommon.BytesToAddress(bz) } func (_ keEthAddr) Stringify(value EthAddr) string { return value.Hex() } @@ -74,6 +74,6 @@ type keEthHash struct{} func (_ keEthHash) Encode(value EthHash) []byte { return value.Bytes() } func (_ keEthHash) Decode(bz []byte) (int, EthHash) { - return ethcommon.HashLength, ethcommon.BytesToHash(bz) + return gethcommon.HashLength, gethcommon.BytesToHash(bz) } func (_ keEthHash) Stringify(value EthHash) string { return value.Hex() } diff --git a/proto/eth/evm/v1/evm.proto b/proto/eth/evm/v1/evm.proto index ed822a175..4ce9aeeda 100644 --- a/proto/eth/evm/v1/evm.proto +++ b/proto/eth/evm/v1/evm.proto @@ -6,6 +6,26 @@ import "gogoproto/gogo.proto"; option go_package = "github.com/NibiruChain/nibiru/x/evm"; +// FunToken is a fungible token mapping between a bank coin and a corresponding +// ERC-20 smart contract. Bank coins here refer to tokens like NIBI, IBC +// coins (ICS-20), and token factory coins, which are each represented by the +// "Coin" type in Golang. +message FunToken { + // Hexadecimal address of the ERC20 token to which the `FunToken` maps + string erc20_addr = 1 [ + (gogoproto.customtype) = "github.com/NibiruChain/nibiru/eth.HexAddr", + (gogoproto.nullable) = false + ]; + + // bank_denom: Coin denomination in the Bank Module. + string bank_denom = 2; + + // True if the `FunToken` mapping was created from an existing bank coin and + // the ERC-20 contract gets deployed by the module account. False if the + // mapping was created from an externally owned ERC-20 contract. + bool is_made_from_coin = 3; +} + // Params defines the EVM module parameters message Params { option (gogoproto.equal) = true; diff --git a/proto/eth/evm/v1/genesis.proto b/proto/eth/evm/v1/genesis.proto index a0a59a248..384fdfa5b 100644 --- a/proto/eth/evm/v1/genesis.proto +++ b/proto/eth/evm/v1/genesis.proto @@ -11,8 +11,12 @@ option go_package = "github.com/NibiruChain/nibiru/x/evm"; message GenesisState { // accounts is an array containing the ethereum genesis accounts. repeated GenesisAccount accounts = 1 [(gogoproto.nullable) = false]; + // params defines all the parameters of the module. Params params = 2 [(gogoproto.nullable) = false]; + + // Fungible token mappings corresponding to ERC-20 smart contract tokens. + repeated eth.evm.v1.FunToken funtoken_mappings = 3 [(gogoproto.nullable) = false]; } // GenesisAccount defines an account to be initialized in the genesis state. diff --git a/x/evm/access_list_test.go b/x/evm/access_list_test.go index c66de410b..b3c8df00f 100644 --- a/x/evm/access_list_test.go +++ b/x/evm/access_list_test.go @@ -1,3 +1,4 @@ +// Copyright (c) 2023-2024 Nibi, Inc. package evm_test import ( diff --git a/x/evm/const.go b/x/evm/const.go index 7188b4209..aaf521983 100644 --- a/x/evm/const.go +++ b/x/evm/const.go @@ -28,6 +28,13 @@ const ( KeyPrefixAccState KeyPrefixParams KeyPrefixEthAddrIndex + + // KV store prefix for `FunToken` mappings + KeyPrefixFunTokens + // KV store prefix for indexing `FunToken` by ERC-20 address + KeyPrefixFunTokenIdxErc20 + // KV store prefix for indexing `FunToken` by bank coin denomination + KeyPrefixFunTokenIdxBankDenom ) // KVStore transient prefix namespaces for the EVM Module. Transient stores only diff --git a/x/evm/events.go b/x/evm/events.go index 3b89eac17..3bf3dad0a 100644 --- a/x/evm/events.go +++ b/x/evm/events.go @@ -7,19 +7,15 @@ const ( EventTypeBlockBloom = "block_bloom" EventTypeTxLog = "tx_log" - AttributeKeyContractAddress = "contract" - AttributeKeyRecipient = "recipient" - AttributeKeyTxHash = "txHash" - AttributeKeyEthereumTxHash = "ethereumTxHash" - AttributeKeyTxIndex = "txIndex" - AttributeKeyTxGasUsed = "txGasUsed" - AttributeKeyTxType = "txType" - AttributeKeyTxLog = "txLog" + AttributeKeyRecipient = "recipient" + AttributeKeyTxHash = "txHash" + AttributeKeyEthereumTxHash = "ethereumTxHash" + AttributeKeyTxIndex = "txIndex" + AttributeKeyTxGasUsed = "txGasUsed" + AttributeKeyTxType = "txType" + AttributeKeyTxLog = "txLog" // tx failed in eth vm execution AttributeKeyEthereumTxFailed = "ethereumTxFailed" AttributeValueCategory = ModuleName AttributeKeyEthereumBloom = "bloom" - - MetricKeyTransitionDB = "transition_db" - MetricKeyStaticCall = "static_call" ) diff --git a/x/evm/evm.go b/x/evm/evm.go new file mode 100644 index 000000000..08612c9d9 --- /dev/null +++ b/x/evm/evm.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package evm + +import ( + "fmt" + + "github.com/cometbft/cometbft/crypto/tmhash" + sdk "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/eth" +) + +// FIXME: Explore problems arrising from ERC1155 creating multiple fungible +// tokens that are valid ERC20s with the same address. +// https://github.com/NibiruChain/nibiru/issues/1933 +func (fun FunToken) ID() []byte { + return newFunTokenIDFromStr(fun.Erc20Addr.String(), fun.BankDenom) +} + +func NewFunTokenID(erc20 gethcommon.Address, bankDenom string) []byte { + erc20Addr := erc20.Hex() + return newFunTokenIDFromStr(erc20Addr, bankDenom) +} + +func newFunTokenIDFromStr(erc20AddrHex string, bankDenom string) []byte { + return tmhash.Sum([]byte(erc20AddrHex + "|" + bankDenom)) +} + +func errValidateFunToken(errMsg string) error { + return fmt.Errorf("FunTokenError: %s", errMsg) +} + +func (fun FunToken) Validate() error { + if err := sdk.ValidateDenom(fun.BankDenom); err != nil { + return errValidateFunToken(err.Error()) + } + + if err := fun.Erc20Addr.Valid(); err != nil { + return errValidateFunToken(err.Error()) + } + + return nil +} + +// NewFunToken is a canonical constructor for the [FunToken] type. Using this +// function helps guarantee a consistent string representation from the +// hex-encoded Ethereum address. +func NewFunToken( + erc20 gethcommon.Address, bankDenom string, isMadeFromCoin bool, +) FunToken { + return FunToken{ + Erc20Addr: eth.NewHexAddr(erc20), + BankDenom: bankDenom, + IsMadeFromCoin: isMadeFromCoin, + } +} diff --git a/x/evm/evm.pb.go b/x/evm/evm.pb.go index 7da40c94b..8607ee45b 100644 --- a/x/evm/evm.pb.go +++ b/x/evm/evm.pb.go @@ -5,6 +5,7 @@ package evm import ( fmt "fmt" + github_com_NibiruChain_nibiru_eth "github.com/NibiruChain/nibiru/eth" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -23,6 +24,68 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// FunToken is a fungible token mapping between a bank coin and a corresponding +// ERC-20 smart contract. Bank coins here refer to tokens like NIBI, IBC +// coins (ICS-20), and token factory coins, which are each represented by the +// "Coin" type in Golang. +type FunToken struct { + // Hexadecimal address of the ERC20 token to which the `FunToken` maps + Erc20Addr github_com_NibiruChain_nibiru_eth.HexAddr `protobuf:"bytes,1,opt,name=erc20_addr,json=erc20Addr,proto3,customtype=github.com/NibiruChain/nibiru/eth.HexAddr" json:"erc20_addr"` + // bank_denom: Coin denomination in the Bank Module. + BankDenom string `protobuf:"bytes,2,opt,name=bank_denom,json=bankDenom,proto3" json:"bank_denom,omitempty"` + // True if the `FunToken` mapping was created from an existing bank coin and + // the ERC-20 contract gets deployed by the module account. False if the + // mapping was created from an externally owned ERC-20 contract. + IsMadeFromCoin bool `protobuf:"varint,3,opt,name=is_made_from_coin,json=isMadeFromCoin,proto3" json:"is_made_from_coin,omitempty"` +} + +func (m *FunToken) Reset() { *m = FunToken{} } +func (m *FunToken) String() string { return proto.CompactTextString(m) } +func (*FunToken) ProtoMessage() {} +func (*FunToken) Descriptor() ([]byte, []int) { + return fileDescriptor_98abbdadb327b7d0, []int{0} +} +func (m *FunToken) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FunToken) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FunToken.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 *FunToken) XXX_Merge(src proto.Message) { + xxx_messageInfo_FunToken.Merge(m, src) +} +func (m *FunToken) XXX_Size() int { + return m.Size() +} +func (m *FunToken) XXX_DiscardUnknown() { + xxx_messageInfo_FunToken.DiscardUnknown(m) +} + +var xxx_messageInfo_FunToken proto.InternalMessageInfo + +func (m *FunToken) GetBankDenom() string { + if m != nil { + return m.BankDenom + } + return "" +} + +func (m *FunToken) GetIsMadeFromCoin() bool { + if m != nil { + return m.IsMadeFromCoin + } + return false +} + // Params defines the EVM module parameters type Params struct { // evm_denom represents the token denomination used to run the EVM state @@ -48,7 +111,7 @@ func (m *Params) Reset() { *m = Params{} } func (m *Params) String() string { return proto.CompactTextString(m) } func (*Params) ProtoMessage() {} func (*Params) Descriptor() ([]byte, []int) { - return fileDescriptor_98abbdadb327b7d0, []int{0} + return fileDescriptor_98abbdadb327b7d0, []int{1} } func (m *Params) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -138,7 +201,7 @@ func (m *State) Reset() { *m = State{} } func (m *State) String() string { return proto.CompactTextString(m) } func (*State) ProtoMessage() {} func (*State) Descriptor() ([]byte, []int) { - return fileDescriptor_98abbdadb327b7d0, []int{1} + return fileDescriptor_98abbdadb327b7d0, []int{2} } func (m *State) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -195,7 +258,7 @@ func (m *TransactionLogs) Reset() { *m = TransactionLogs{} } func (m *TransactionLogs) String() string { return proto.CompactTextString(m) } func (*TransactionLogs) ProtoMessage() {} func (*TransactionLogs) Descriptor() ([]byte, []int) { - return fileDescriptor_98abbdadb327b7d0, []int{2} + return fileDescriptor_98abbdadb327b7d0, []int{3} } func (m *TransactionLogs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -271,7 +334,7 @@ func (m *Log) Reset() { *m = Log{} } func (m *Log) String() string { return proto.CompactTextString(m) } func (*Log) ProtoMessage() {} func (*Log) Descriptor() ([]byte, []int) { - return fileDescriptor_98abbdadb327b7d0, []int{3} + return fileDescriptor_98abbdadb327b7d0, []int{4} } func (m *Log) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -386,7 +449,7 @@ func (m *TxResult) Reset() { *m = TxResult{} } func (m *TxResult) String() string { return proto.CompactTextString(m) } func (*TxResult) ProtoMessage() {} func (*TxResult) Descriptor() ([]byte, []int) { - return fileDescriptor_98abbdadb327b7d0, []int{4} + return fileDescriptor_98abbdadb327b7d0, []int{5} } func (m *TxResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -427,7 +490,7 @@ func (m *AccessTuple) Reset() { *m = AccessTuple{} } func (m *AccessTuple) String() string { return proto.CompactTextString(m) } func (*AccessTuple) ProtoMessage() {} func (*AccessTuple) Descriptor() ([]byte, []int) { - return fileDescriptor_98abbdadb327b7d0, []int{5} + return fileDescriptor_98abbdadb327b7d0, []int{6} } func (m *AccessTuple) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -485,7 +548,7 @@ func (m *TraceConfig) Reset() { *m = TraceConfig{} } func (m *TraceConfig) String() string { return proto.CompactTextString(m) } func (*TraceConfig) ProtoMessage() {} func (*TraceConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_98abbdadb327b7d0, []int{6} + return fileDescriptor_98abbdadb327b7d0, []int{7} } func (m *TraceConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -585,6 +648,7 @@ func (m *TraceConfig) GetTracerJsonConfig() string { } func init() { + proto.RegisterType((*FunToken)(nil), "eth.evm.v1.FunToken") proto.RegisterType((*Params)(nil), "eth.evm.v1.Params") proto.RegisterType((*State)(nil), "eth.evm.v1.State") proto.RegisterType((*TransactionLogs)(nil), "eth.evm.v1.TransactionLogs") @@ -597,73 +661,78 @@ func init() { func init() { proto.RegisterFile("eth/evm/v1/evm.proto", fileDescriptor_98abbdadb327b7d0) } var fileDescriptor_98abbdadb327b7d0 = []byte{ - // 1041 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0x41, 0x6f, 0xdb, 0x46, - 0x13, 0xb5, 0x2c, 0x4a, 0xa2, 0x56, 0x72, 0xa4, 0x6c, 0xf4, 0xe5, 0x63, 0x13, 0x40, 0x14, 0x98, - 0x8b, 0x0e, 0xa9, 0x05, 0xbb, 0x28, 0x0a, 0xb8, 0x48, 0x01, 0xd3, 0x71, 0xd1, 0xa8, 0x4e, 0x60, - 0x6c, 0x9c, 0x1e, 0x7a, 0x21, 0x56, 0xe4, 0x94, 0x62, 0x4d, 0x72, 0x05, 0xee, 0x4a, 0xa5, 0x7f, - 0x40, 0x81, 0x1e, 0x7b, 0xed, 0x2d, 0x3f, 0x27, 0xe8, 0x29, 0xc7, 0x9e, 0x88, 0xc2, 0xbe, 0xb4, - 0x3a, 0xea, 0x17, 0x14, 0xbb, 0x4b, 0x59, 0xb2, 0x0b, 0xf4, 0xa4, 0x79, 0xf3, 0x76, 0x76, 0x86, - 0x6f, 0x1e, 0x29, 0xd4, 0x03, 0x31, 0x1d, 0xc1, 0x22, 0x19, 0x2d, 0x0e, 0xe4, 0xcf, 0xfe, 0x2c, - 0x63, 0x82, 0x61, 0x04, 0x62, 0xba, 0x2f, 0xe1, 0xe2, 0xe0, 0x49, 0x2f, 0x64, 0x21, 0x53, 0xe9, - 0x91, 0x8c, 0xf4, 0x09, 0xe7, 0xb7, 0x2a, 0xaa, 0x9f, 0xd3, 0x8c, 0x26, 0x1c, 0x1f, 0xa0, 0x26, - 0x2c, 0x12, 0x2f, 0x80, 0x94, 0x25, 0x56, 0x65, 0x50, 0x19, 0x36, 0xdd, 0xde, 0xaa, 0xb0, 0xbb, - 0x57, 0x34, 0x89, 0x8f, 0x9c, 0x5b, 0xca, 0x21, 0x26, 0x2c, 0x92, 0x97, 0x32, 0xc4, 0x2f, 0xd0, - 0x1e, 0xa4, 0x74, 0x12, 0x83, 0xe7, 0x67, 0x40, 0x05, 0x58, 0xbb, 0x83, 0xca, 0xd0, 0x74, 0xad, - 0x55, 0x61, 0xf7, 0xca, 0xb2, 0x6d, 0xda, 0x21, 0x6d, 0x8d, 0x4f, 0x14, 0xc4, 0x5f, 0xa0, 0xd6, - 0x9a, 0xa7, 0x71, 0x6c, 0x55, 0x55, 0xf1, 0xe3, 0x55, 0x61, 0xe3, 0xbb, 0xc5, 0x34, 0x8e, 0x1d, - 0x82, 0xca, 0x52, 0x1a, 0xc7, 0xf8, 0x18, 0x21, 0xc8, 0x45, 0x46, 0x3d, 0x88, 0x66, 0xdc, 0x32, - 0x06, 0xd5, 0x61, 0xd5, 0x75, 0xae, 0x0b, 0xbb, 0x79, 0x2a, 0xb3, 0xa7, 0xaf, 0xce, 0xf9, 0xaa, - 0xb0, 0x1f, 0x96, 0x97, 0xdc, 0x1e, 0x74, 0x48, 0x53, 0x81, 0xd3, 0x68, 0xc6, 0xf1, 0x21, 0xfa, - 0x1f, 0x8d, 0x63, 0xf6, 0x93, 0x37, 0x4f, 0xa5, 0x12, 0xe0, 0x0b, 0x08, 0x3c, 0x91, 0x73, 0xab, - 0x2e, 0xa7, 0x20, 0x8f, 0x14, 0xf9, 0x6e, 0xc3, 0x5d, 0xe4, 0x1c, 0x7f, 0x8a, 0x30, 0xf5, 0x45, - 0xb4, 0x00, 0x6f, 0x96, 0x81, 0xcf, 0x92, 0x59, 0x14, 0x03, 0xb7, 0x1a, 0x83, 0xea, 0xb0, 0x49, - 0x1e, 0x6a, 0xe6, 0x7c, 0x43, 0xe0, 0x43, 0xd4, 0x96, 0xaa, 0xf9, 0x53, 0x9a, 0xa6, 0x10, 0x73, - 0xcb, 0x94, 0x07, 0xdd, 0xce, 0x75, 0x61, 0xb7, 0x4e, 0xbf, 0x7b, 0x7d, 0x52, 0xa6, 0x49, 0x0b, - 0x16, 0xc9, 0x1a, 0x1c, 0x19, 0x7f, 0xbd, 0xb7, 0x2b, 0x63, 0xc3, 0xac, 0x75, 0xeb, 0xce, 0x08, - 0xd5, 0xde, 0x0a, 0xa9, 0x53, 0x17, 0x55, 0x2f, 0xe1, 0x4a, 0xef, 0x84, 0xc8, 0x10, 0xf7, 0x50, - 0x6d, 0x41, 0xe3, 0xb9, 0x16, 0xbc, 0x49, 0x34, 0x70, 0xc6, 0xa8, 0x73, 0x91, 0xd1, 0x94, 0xcb, - 0x51, 0x58, 0x7a, 0xc6, 0x42, 0x8e, 0x31, 0x32, 0xa6, 0x94, 0x4f, 0xcb, 0x5a, 0x15, 0xe3, 0x67, - 0xc8, 0x88, 0x59, 0xc8, 0xad, 0xdd, 0x41, 0x75, 0xd8, 0x3a, 0xec, 0xec, 0x6f, 0x4c, 0xb2, 0x7f, - 0xc6, 0x42, 0xa2, 0x48, 0xe7, 0xf7, 0x5d, 0x54, 0x3d, 0x63, 0x21, 0xb6, 0x50, 0x83, 0x06, 0x41, - 0x06, 0x9c, 0x97, 0x77, 0xac, 0x21, 0x7e, 0x8c, 0xea, 0x82, 0xcd, 0x22, 0x5f, 0x5f, 0xd4, 0x24, - 0x25, 0x92, 0x2d, 0x03, 0x2a, 0xa8, 0x5a, 0x67, 0x9b, 0xa8, 0x58, 0x4a, 0x31, 0x89, 0x99, 0x7f, - 0xe9, 0xa5, 0xf3, 0x64, 0x02, 0x99, 0x65, 0x0c, 0x2a, 0x43, 0xc3, 0xed, 0x2c, 0x0b, 0xbb, 0xa5, - 0xf2, 0x6f, 0x54, 0x9a, 0x6c, 0x03, 0xfc, 0x1c, 0x35, 0x44, 0xee, 0xa9, 0xe9, 0x6b, 0xca, 0x8d, - 0x8f, 0x96, 0x85, 0xdd, 0x11, 0x9b, 0x07, 0xfc, 0x86, 0xf2, 0x29, 0xa9, 0x8b, 0x5c, 0xfe, 0xe2, - 0x11, 0x32, 0x45, 0xee, 0x45, 0x69, 0x00, 0xb9, 0x5a, 0xa1, 0xe1, 0xf6, 0x96, 0x85, 0xdd, 0xdd, - 0x3a, 0xfe, 0x4a, 0x72, 0xa4, 0x21, 0x72, 0x15, 0xe0, 0xe7, 0x08, 0xe9, 0x91, 0x54, 0x87, 0x86, - 0xea, 0xb0, 0xb7, 0x2c, 0xec, 0xa6, 0xca, 0xaa, 0xbb, 0x37, 0x21, 0x76, 0x50, 0x4d, 0xdf, 0x6d, - 0xaa, 0xbb, 0xdb, 0xcb, 0xc2, 0x36, 0x63, 0x16, 0xea, 0x3b, 0x35, 0x25, 0xa5, 0xca, 0x20, 0x61, - 0x0b, 0x08, 0xac, 0xa6, 0x32, 0xd1, 0x1a, 0x3a, 0x3f, 0xef, 0x22, 0xf3, 0x22, 0x27, 0xc0, 0xe7, - 0xb1, 0xc0, 0x5f, 0xa3, 0xae, 0xcf, 0x52, 0x91, 0x51, 0x5f, 0x78, 0x77, 0xa4, 0x75, 0x9f, 0xae, - 0x0a, 0xfb, 0xff, 0xda, 0xb5, 0xf7, 0x4f, 0x38, 0xa4, 0xb3, 0x4e, 0x1d, 0x97, 0xfa, 0xf7, 0x50, - 0x6d, 0x12, 0x33, 0x96, 0x28, 0x0f, 0xb4, 0x89, 0x06, 0xf8, 0x4c, 0xa9, 0xa6, 0xf6, 0x2b, 0x17, - 0xd0, 0x3a, 0x7c, 0xba, 0xbd, 0xdf, 0x7b, 0xf6, 0x70, 0x1f, 0x7f, 0x28, 0xec, 0x9d, 0x55, 0x61, - 0x3f, 0xd0, 0x5d, 0xcb, 0x4a, 0x47, 0xaa, 0xaa, 0xec, 0xd3, 0x45, 0xd5, 0x0c, 0x84, 0x5a, 0x57, - 0x9b, 0xc8, 0x10, 0x3f, 0x41, 0x66, 0x06, 0x0b, 0xc8, 0x04, 0x04, 0x6a, 0x2d, 0x26, 0xb9, 0xc5, - 0xf8, 0x13, 0x64, 0x86, 0x94, 0x7b, 0x73, 0x0e, 0x81, 0xde, 0x01, 0x69, 0x84, 0x94, 0xbf, 0xe3, - 0x10, 0x1c, 0x19, 0xbf, 0xbc, 0xb7, 0x77, 0x1c, 0x8a, 0x5a, 0xc7, 0xbe, 0x0f, 0x9c, 0x5f, 0xcc, - 0x67, 0x31, 0xfc, 0x87, 0xb7, 0x0e, 0x51, 0x9b, 0x0b, 0x96, 0xd1, 0x10, 0xbc, 0x4b, 0xb8, 0x2a, - 0x1d, 0xa6, 0xfd, 0x52, 0xe6, 0xbf, 0x85, 0x2b, 0x4e, 0xb6, 0x41, 0xd9, 0xe2, 0xef, 0x2a, 0x6a, - 0x5d, 0x64, 0xd4, 0x87, 0x13, 0x96, 0xfe, 0x10, 0x85, 0xca, 0xa5, 0x12, 0x66, 0x65, 0x8b, 0x12, - 0xc9, 0xde, 0x22, 0x4a, 0x80, 0xcd, 0x45, 0xf9, 0x0e, 0xad, 0xa1, 0xac, 0xc8, 0x00, 0x72, 0xf0, - 0x95, 0x80, 0x06, 0x29, 0x11, 0xfe, 0x1c, 0xed, 0x05, 0x11, 0x57, 0x5f, 0x24, 0x2e, 0xa8, 0x7f, - 0xa9, 0x1f, 0xdf, 0xed, 0x2e, 0x0b, 0xbb, 0x5d, 0x12, 0x6f, 0x65, 0x9e, 0xdc, 0x41, 0xf8, 0x4b, - 0xd4, 0xd9, 0x94, 0xa9, 0x69, 0xf5, 0x27, 0xc6, 0xc5, 0xcb, 0xc2, 0x7e, 0x70, 0x7b, 0x54, 0x31, - 0xe4, 0x1e, 0x96, 0x3b, 0x0e, 0x60, 0x32, 0x0f, 0x95, 0xed, 0x4c, 0xa2, 0x81, 0xcc, 0xc6, 0x51, - 0x12, 0x09, 0x65, 0xb3, 0x1a, 0xd1, 0x40, 0xce, 0x57, 0x7e, 0x30, 0x13, 0x48, 0x58, 0x76, 0x65, - 0xb5, 0x36, 0xf3, 0x69, 0xe2, 0xb5, 0xca, 0x93, 0x3b, 0x08, 0xbb, 0x08, 0x97, 0x65, 0x19, 0x88, - 0x79, 0x96, 0x7a, 0xea, 0xe5, 0x6d, 0xab, 0x5a, 0xf5, 0x0a, 0x69, 0x96, 0x28, 0xf2, 0x25, 0x15, - 0x94, 0xfc, 0x2b, 0x83, 0xbf, 0x42, 0x58, 0xcb, 0xea, 0xfd, 0xc8, 0x59, 0xea, 0xf9, 0x4a, 0x7a, - 0x6b, 0x4f, 0x99, 0x5a, 0xf5, 0xd7, 0xac, 0x5e, 0x09, 0xe9, 0x6a, 0x34, 0xe6, 0x2c, 0xd5, 0x99, - 0xb1, 0x61, 0x1a, 0xdd, 0xda, 0xd8, 0x30, 0x1b, 0x5d, 0x73, 0x6c, 0x98, 0xa8, 0xdb, 0xba, 0x15, - 0xa2, 0x7c, 0x16, 0xf2, 0x68, 0x8d, 0xb7, 0x86, 0x74, 0x5f, 0x7c, 0xb8, 0xee, 0x57, 0x3e, 0x5e, - 0xf7, 0x2b, 0x7f, 0x5e, 0xf7, 0x2b, 0xbf, 0xde, 0xf4, 0x77, 0x3e, 0xde, 0xf4, 0x77, 0xfe, 0xb8, - 0xe9, 0xef, 0x7c, 0xff, 0x2c, 0x8c, 0xc4, 0x74, 0x3e, 0xd9, 0xf7, 0x59, 0x32, 0x7a, 0x13, 0x4d, - 0xa2, 0x6c, 0x7e, 0x32, 0xa5, 0x51, 0x3a, 0x4a, 0x55, 0x3c, 0xca, 0xe5, 0x7f, 0xe4, 0xa4, 0xae, - 0xfe, 0x02, 0x3f, 0xfb, 0x27, 0x00, 0x00, 0xff, 0xff, 0x43, 0x47, 0xd2, 0x97, 0x3c, 0x07, 0x00, - 0x00, + // 1135 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0xcf, 0x6e, 0xdb, 0xc6, + 0x13, 0xb6, 0x2c, 0xca, 0xa2, 0x56, 0x72, 0xa4, 0x6c, 0xf4, 0xcb, 0x8f, 0x4d, 0x50, 0xd3, 0x60, + 0x2e, 0x0e, 0x90, 0x5a, 0xb5, 0x8b, 0xa2, 0x40, 0x8a, 0x14, 0x30, 0x1d, 0x07, 0x89, 0xeb, 0x04, + 0xc6, 0xc6, 0xe9, 0xa1, 0x17, 0x62, 0x45, 0x4e, 0x28, 0xd6, 0x24, 0x57, 0xd8, 0x5d, 0xaa, 0xf4, + 0x03, 0x14, 0xe8, 0xb1, 0xd7, 0xde, 0x72, 0xea, 0xb3, 0x04, 0x3d, 0xe5, 0x58, 0xf4, 0x40, 0x14, + 0xce, 0xa5, 0xd5, 0xd1, 0x4f, 0x50, 0xec, 0x2e, 0x65, 0xd9, 0x29, 0x90, 0x13, 0xe7, 0xfb, 0xbe, + 0x9d, 0x3f, 0x9c, 0x19, 0x2e, 0xd1, 0x10, 0xe4, 0x64, 0x04, 0xb3, 0x6c, 0x34, 0xdb, 0x51, 0x8f, + 0xed, 0x29, 0x67, 0x92, 0x61, 0x04, 0x72, 0xb2, 0xad, 0xe0, 0x6c, 0xe7, 0xce, 0x30, 0x66, 0x31, + 0xd3, 0xf4, 0x48, 0x59, 0xe6, 0x84, 0xf7, 0x5b, 0x03, 0xd9, 0x4f, 0x8a, 0xfc, 0x84, 0x9d, 0x42, + 0x8e, 0x8f, 0x11, 0x02, 0x1e, 0xee, 0x7e, 0x1e, 0xd0, 0x28, 0xe2, 0x4e, 0x63, 0xb3, 0xb1, 0xd5, + 0xf1, 0x77, 0xde, 0x56, 0xee, 0xca, 0x9f, 0x95, 0x7b, 0x3f, 0x4e, 0xe4, 0xa4, 0x18, 0x6f, 0x87, + 0x2c, 0x1b, 0xbd, 0x48, 0xc6, 0x09, 0x2f, 0xf6, 0x27, 0x34, 0xc9, 0x47, 0xb9, 0xb6, 0x47, 0x2a, + 0xd1, 0x53, 0x28, 0xf7, 0xa2, 0x88, 0x93, 0x8e, 0x0e, 0xa2, 0x4c, 0xfc, 0x29, 0x42, 0x63, 0x9a, + 0x9f, 0x06, 0x11, 0xe4, 0x2c, 0x73, 0x56, 0x55, 0x44, 0xd2, 0x51, 0xcc, 0x63, 0x45, 0xe0, 0xfb, + 0xe8, 0x66, 0x22, 0x82, 0x8c, 0x46, 0x10, 0xbc, 0xe6, 0x2c, 0x0b, 0x42, 0x96, 0xe4, 0x4e, 0x73, + 0xb3, 0xb1, 0x65, 0x93, 0x1b, 0x89, 0x78, 0x4e, 0x23, 0x78, 0xc2, 0x59, 0xb6, 0xcf, 0x92, 0xdc, + 0xfb, 0xb5, 0x89, 0xd6, 0x8e, 0x29, 0xa7, 0x99, 0xc0, 0x3b, 0xa8, 0x03, 0xb3, 0xac, 0x8e, 0x69, + 0xaa, 0x1c, 0x5e, 0x54, 0xee, 0xe0, 0x8c, 0x66, 0xe9, 0x43, 0xef, 0x52, 0xf2, 0x88, 0x0d, 0xb3, + 0xcc, 0x24, 0x7a, 0x84, 0xd6, 0x21, 0xa7, 0xe3, 0x14, 0x82, 0x90, 0x03, 0x95, 0xa0, 0x4b, 0xb1, + 0x7d, 0xe7, 0xa2, 0x72, 0x87, 0xb5, 0xdb, 0x55, 0xd9, 0x23, 0x3d, 0x83, 0xf7, 0x35, 0xc4, 0x5f, + 0xa1, 0xee, 0x42, 0xa7, 0x69, 0x6a, 0x2a, 0xf4, 0x6f, 0x5f, 0x54, 0x2e, 0xbe, 0xee, 0x4c, 0xd3, + 0xd4, 0x23, 0xa8, 0x76, 0xa5, 0x69, 0x8a, 0xf7, 0x10, 0x82, 0x52, 0x72, 0x1a, 0x40, 0x32, 0x15, + 0x8e, 0xb5, 0xd9, 0xdc, 0x6a, 0xfa, 0xde, 0x79, 0xe5, 0x76, 0x0e, 0x14, 0x7b, 0xf0, 0xec, 0x58, + 0x5c, 0x54, 0xee, 0xcd, 0x3a, 0xc8, 0xe5, 0x41, 0x8f, 0x74, 0x34, 0x38, 0x48, 0xa6, 0x02, 0xef, + 0xa2, 0xff, 0xd1, 0x34, 0x65, 0x3f, 0x06, 0x45, 0xae, 0x46, 0x06, 0xa1, 0x84, 0x28, 0x90, 0xa5, + 0x70, 0xd6, 0x74, 0x9f, 0x6e, 0x69, 0xf1, 0xd5, 0x52, 0x3b, 0x29, 0x05, 0xfe, 0x0c, 0x61, 0x1a, + 0xca, 0x64, 0x06, 0xc1, 0x94, 0x43, 0xc8, 0xb2, 0x69, 0x92, 0x82, 0x70, 0xda, 0x9b, 0xcd, 0xad, + 0x0e, 0xb9, 0x69, 0x94, 0xe3, 0xa5, 0x80, 0x77, 0x51, 0x4f, 0x75, 0x2d, 0x9c, 0xd0, 0x3c, 0x87, + 0x54, 0x38, 0xb6, 0x3a, 0xe8, 0xf7, 0xcf, 0x2b, 0xb7, 0x7b, 0xf0, 0xdd, 0xf3, 0xfd, 0x9a, 0x26, + 0x5d, 0x98, 0x65, 0x0b, 0xf0, 0xd0, 0xfa, 0xfb, 0x8d, 0xdb, 0x38, 0xb4, 0xec, 0xd6, 0x60, 0xcd, + 0x1b, 0xa1, 0xd6, 0x4b, 0xa9, 0xfa, 0x34, 0x40, 0xcd, 0x53, 0x38, 0x33, 0x33, 0x21, 0xca, 0xc4, + 0x43, 0xd4, 0x9a, 0xd1, 0xb4, 0x80, 0x7a, 0xf6, 0x06, 0x78, 0x87, 0xa8, 0x7f, 0xc2, 0x69, 0x2e, + 0x54, 0x29, 0x2c, 0x3f, 0x62, 0xb1, 0xc0, 0x18, 0x59, 0x13, 0x2a, 0x26, 0xb5, 0xaf, 0xb6, 0xf1, + 0x3d, 0x64, 0xa5, 0x2c, 0x16, 0xce, 0xea, 0x66, 0x73, 0xab, 0xbb, 0xdb, 0xdf, 0x5e, 0x6e, 0xf3, + 0xf6, 0x11, 0x8b, 0x89, 0x16, 0xbd, 0xdf, 0x57, 0x51, 0xf3, 0x88, 0xc5, 0xd8, 0x41, 0x6d, 0xb5, + 0xb6, 0x20, 0x44, 0x1d, 0x63, 0x01, 0xf1, 0x6d, 0xb4, 0x26, 0xd9, 0x34, 0x09, 0x4d, 0xa0, 0x0e, + 0xa9, 0x91, 0x4a, 0x19, 0x51, 0x49, 0xf5, 0x38, 0x7b, 0x44, 0xdb, 0xaa, 0x15, 0xe3, 0x94, 0x85, + 0xa7, 0x41, 0x5e, 0x64, 0x63, 0xe0, 0x8e, 0xb5, 0xd9, 0xd8, 0xb2, 0xfc, 0xfe, 0xbc, 0x72, 0xbb, + 0x9a, 0x7f, 0xa1, 0x69, 0x72, 0x15, 0xe0, 0x07, 0xa8, 0x2d, 0xcb, 0x40, 0x57, 0xdf, 0xd2, 0xdb, + 0x78, 0x6b, 0x5e, 0xb9, 0x7d, 0xb9, 0x7c, 0xc1, 0xa7, 0x54, 0x4c, 0xc8, 0x9a, 0x2c, 0xd5, 0x13, + 0x8f, 0x90, 0x2d, 0xcb, 0x20, 0xc9, 0x23, 0x28, 0xf5, 0x08, 0x2d, 0x7f, 0x38, 0xaf, 0xdc, 0xc1, + 0x95, 0xe3, 0xcf, 0x94, 0x46, 0xda, 0xb2, 0xd4, 0x06, 0x7e, 0x80, 0x90, 0x29, 0x49, 0x67, 0x68, + 0xeb, 0x0c, 0xeb, 0xf3, 0xca, 0xed, 0x68, 0x56, 0xc7, 0x5e, 0x9a, 0xd8, 0x43, 0x2d, 0x13, 0xdb, + 0xd6, 0xb1, 0x7b, 0xf3, 0xca, 0xb5, 0x53, 0x16, 0x9b, 0x98, 0x46, 0x52, 0xad, 0xe2, 0x90, 0xb1, + 0x19, 0x44, 0x4e, 0x47, 0x2f, 0xd1, 0x02, 0x7a, 0x3f, 0xad, 0x22, 0xfb, 0xa4, 0x24, 0x20, 0x8a, + 0x54, 0xe2, 0x27, 0x68, 0x10, 0xb2, 0x5c, 0x72, 0x1a, 0xca, 0xe0, 0x5a, 0x6b, 0xfd, 0xbb, 0x17, + 0x95, 0xfb, 0x7f, 0xb3, 0xb5, 0x1f, 0x9e, 0xf0, 0x48, 0x7f, 0x41, 0xed, 0xd5, 0xfd, 0x1f, 0xa2, + 0xd6, 0x38, 0x65, 0xf5, 0xf7, 0xdf, 0x23, 0x06, 0xe0, 0x23, 0xdd, 0x35, 0x3d, 0x5f, 0x35, 0x80, + 0xee, 0xee, 0xdd, 0xab, 0xf3, 0xfd, 0x60, 0x3d, 0xfc, 0xdb, 0xea, 0x1a, 0xba, 0xa8, 0xdc, 0x1b, + 0x26, 0x6b, 0xed, 0xe9, 0xa9, 0xae, 0xea, 0xf5, 0x19, 0xa0, 0x26, 0x07, 0xa9, 0xc7, 0xd5, 0x23, + 0xca, 0xc4, 0x77, 0x90, 0xcd, 0x61, 0x06, 0x5c, 0x42, 0xa4, 0xc7, 0x62, 0x93, 0x4b, 0x8c, 0x3f, + 0x41, 0x76, 0x4c, 0x45, 0x50, 0x08, 0x88, 0xcc, 0x0c, 0x48, 0x3b, 0xa6, 0xe2, 0x95, 0x80, 0xe8, + 0xa1, 0xf5, 0xf3, 0x1b, 0x77, 0xc5, 0xa3, 0xa8, 0xbb, 0x17, 0x86, 0x20, 0xc4, 0x49, 0x31, 0x4d, + 0xe1, 0x23, 0xbb, 0xb5, 0x8b, 0x7a, 0x42, 0x32, 0x4e, 0x63, 0x08, 0x4e, 0xe1, 0xac, 0xde, 0x30, + 0xb3, 0x2f, 0x35, 0xff, 0x2d, 0x9c, 0x09, 0x72, 0x15, 0xd4, 0x29, 0xfe, 0x69, 0xa2, 0xee, 0x09, + 0xa7, 0x21, 0xec, 0xb3, 0xfc, 0x75, 0x12, 0xeb, 0x2d, 0x55, 0xb0, 0xbe, 0x78, 0x49, 0x8d, 0x54, + 0x6e, 0x99, 0x64, 0xc0, 0x0a, 0x59, 0x7f, 0x43, 0x0b, 0xa8, 0x3c, 0x38, 0x40, 0x09, 0xa1, 0x6e, + 0xa0, 0x45, 0x6a, 0x84, 0xbf, 0x44, 0xeb, 0x51, 0x22, 0xf4, 0x8d, 0x24, 0x24, 0x0d, 0x4f, 0xcd, + 0xeb, 0xfb, 0x83, 0x79, 0xe5, 0xf6, 0x6a, 0xe1, 0xa5, 0xe2, 0xc9, 0x35, 0x84, 0xbf, 0x46, 0xfd, + 0xa5, 0x9b, 0xae, 0xd6, 0x5c, 0x31, 0x3e, 0x9e, 0x57, 0xee, 0x8d, 0xcb, 0xa3, 0x5a, 0x21, 0x1f, + 0x60, 0x35, 0xe3, 0x08, 0xc6, 0x45, 0xac, 0xd7, 0xce, 0x26, 0x06, 0x28, 0x36, 0x4d, 0xb2, 0x44, + 0xea, 0x35, 0x6b, 0x11, 0x03, 0x54, 0x7d, 0xf5, 0x85, 0x99, 0x41, 0xc6, 0xf8, 0x99, 0xd3, 0x5d, + 0xd6, 0x67, 0x84, 0xe7, 0x9a, 0x27, 0xd7, 0x10, 0xf6, 0x11, 0xae, 0xdd, 0x38, 0xc8, 0x82, 0xe7, + 0x81, 0xfe, 0x78, 0x7b, 0xda, 0x57, 0x7f, 0x42, 0x46, 0x25, 0x5a, 0x7c, 0x4c, 0x25, 0x25, 0xff, + 0x61, 0xf0, 0x37, 0x08, 0x9b, 0xb6, 0x06, 0x3f, 0x08, 0x96, 0x07, 0xa1, 0x6e, 0xbd, 0xb3, 0xae, + 0x97, 0x5a, 0xe7, 0x37, 0xaa, 0x19, 0x09, 0x19, 0x18, 0x74, 0x28, 0x58, 0x6e, 0x98, 0x43, 0xcb, + 0xb6, 0x06, 0xad, 0x43, 0xcb, 0x6e, 0x0f, 0xec, 0x43, 0xcb, 0x46, 0x83, 0xee, 0x65, 0x23, 0xea, + 0x77, 0x21, 0xb7, 0x16, 0xf8, 0x4a, 0x91, 0xfe, 0xa3, 0xb7, 0xe7, 0x1b, 0x8d, 0x77, 0xe7, 0x1b, + 0x8d, 0xbf, 0xce, 0x37, 0x1a, 0xbf, 0xbc, 0xdf, 0x58, 0x79, 0xf7, 0x7e, 0x63, 0xe5, 0x8f, 0xf7, + 0x1b, 0x2b, 0xdf, 0xdf, 0xfb, 0xf8, 0x6f, 0xb5, 0x54, 0x3f, 0xf3, 0xf1, 0x9a, 0xfe, 0x57, 0x7f, + 0xf1, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x94, 0x04, 0x70, 0x58, 0xe5, 0x07, 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -723,6 +792,56 @@ func (this *Params) Equal(that interface{}) bool { } return true } +func (m *FunToken) 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 *FunToken) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FunToken) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.IsMadeFromCoin { + i-- + if m.IsMadeFromCoin { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if len(m.BankDenom) > 0 { + i -= len(m.BankDenom) + copy(dAtA[i:], m.BankDenom) + i = encodeVarintEvm(dAtA, i, uint64(len(m.BankDenom))) + i-- + dAtA[i] = 0x12 + } + { + size := m.Erc20Addr.Size() + i -= size + if _, err := m.Erc20Addr.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvm(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *Params) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1209,6 +1328,24 @@ func encodeVarintEvm(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } +func (m *FunToken) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Erc20Addr.Size() + n += 1 + l + sovEvm(uint64(l)) + l = len(m.BankDenom) + if l > 0 { + n += 1 + l + sovEvm(uint64(l)) + } + if m.IsMadeFromCoin { + n += 2 + } + return n +} + func (m *Params) Size() (n int) { if m == nil { return 0 @@ -1425,6 +1562,142 @@ func sovEvm(x uint64) (n int) { func sozEvm(x uint64) (n int) { return sovEvm(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *FunToken) 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 ErrIntOverflowEvm + } + 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: FunToken: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FunToken: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Erc20Addr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvm + } + 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 ErrInvalidLengthEvm + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Erc20Addr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BankDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvm + } + 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 ErrInvalidLengthEvm + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BankDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsMadeFromCoin", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsMadeFromCoin = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipEvm(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvm + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Params) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/evm/evm_test.go b/x/evm/evm_test.go new file mode 100644 index 000000000..8c6156a15 --- /dev/null +++ b/x/evm/evm_test.go @@ -0,0 +1,112 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package evm_test + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/evm" +) + +type TestSuite struct { + suite.Suite +} + +func TestSuite_RunAll(t *testing.T) { + suite.Run(t, new(TestSuite)) +} + +func (s *TestSuite) TestFunToken() { + for testIdx, tc := range []struct { + bankDenom string + erc20Addr eth.HexAddr + wantErr string + }{ + { + // sad: Invalid bank denom + bankDenom: "", + erc20Addr: eth.HexAddr(""), + wantErr: "FunTokenError", + }, + { + bankDenom: "unibi", + erc20Addr: eth.MustNewHexAddrFromStr("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"), + wantErr: "", + }, + { + bankDenom: "unibi", + erc20Addr: eth.MustNewHexAddrFromStr("5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED"), + wantErr: "", + }, + { + // NOTE: notice how this one errors using the same happy path + // input as above because an unsafe constructor was used. + // Naked type overrides should not be used with eth.HexAddr. + // Always use NewHexAddr, NewHexAddrFromStr, or MustNewHexAddr... + bankDenom: "unibi", + erc20Addr: eth.HexAddr("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"), + wantErr: "not encoded as expected", + }, + + { + bankDenom: "ibc/AAA/BBB", + erc20Addr: eth.MustNewHexAddrFromStr("0xE1aA1500b962528cBB42F05bD6d8A6032a85602f"), + wantErr: "", + }, + { + bankDenom: "tf/contract-addr/subdenom", + erc20Addr: eth.MustNewHexAddrFromStr("0x6B2e60f1030aFa69F584829f1d700b47eE5Fc74a"), + wantErr: "", + }, + } { + s.Run(strconv.Itoa(testIdx), func() { + funtoken := evm.FunToken{ + Erc20Addr: tc.erc20Addr, + BankDenom: tc.bankDenom, + } + err := funtoken.Validate() + if tc.wantErr != "" { + s.Require().Error(err, "funtoken %s", funtoken) + return + } + s.Require().NoError(err) + }) + } + + for _, tc := range []struct { + name string + A string + B string + }{ + { + name: "capital and lowercase match", + A: "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + B: "5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED", + }, + { + name: "0x prefix and no prefix match", + A: "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + B: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + }, + { + name: "0x prefix and no prefix match", + A: "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + B: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + }, + { + name: "mixed case compatibility", + A: "0x5Bdb32670a05Daa22Cb2E279B80044c37dc85e61", + B: "0x5BDB32670A05DAA22CB2E279B80044C37DC85E61", + }, + } { + s.Run(tc.name, func() { + funA := evm.FunToken{Erc20Addr: eth.HexAddr(tc.A)} + funB := evm.FunToken{Erc20Addr: eth.HexAddr(tc.B)} + + s.EqualValues(funA.Erc20Addr.ToAddr(), funB.Erc20Addr.ToAddr()) + }) + } +} diff --git a/x/evm/genesis.pb.go b/x/evm/genesis.pb.go index 3d7f9ada9..0f8a9980b 100644 --- a/x/evm/genesis.pb.go +++ b/x/evm/genesis.pb.go @@ -29,6 +29,8 @@ type GenesisState struct { Accounts []GenesisAccount `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts"` // params defines all the parameters of the module. Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` + // Fungible token mappings corresponding to ERC-20 smart contract tokens. + FuntokenMappings []FunToken `protobuf:"bytes,3,rep,name=funtoken_mappings,json=funtokenMappings,proto3" json:"funtoken_mappings"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -78,6 +80,13 @@ func (m *GenesisState) GetParams() Params { return Params{} } +func (m *GenesisState) GetFuntokenMappings() []FunToken { + if m != nil { + return m.FuntokenMappings + } + return nil +} + // GenesisAccount defines an account to be initialized in the genesis state. // Its main difference between with Geth's GenesisAccount is that it uses a // custom storage type and that it doesn't contain the private key field. @@ -152,26 +161,28 @@ func init() { func init() { proto.RegisterFile("eth/evm/v1/genesis.proto", fileDescriptor_d41c81841e3983b5) } var fileDescriptor_d41c81841e3983b5 = []byte{ - // 297 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x90, 0x3f, 0x4e, 0xc3, 0x30, - 0x18, 0xc5, 0x63, 0x5a, 0x35, 0xd4, 0x45, 0x20, 0xac, 0x0e, 0x51, 0x06, 0xb7, 0x2a, 0x4b, 0x26, - 0x9b, 0x96, 0xb5, 0x0c, 0x84, 0x81, 0x0d, 0xa1, 0x74, 0x63, 0x73, 0x12, 0x2b, 0xc9, 0x90, 0xb8, - 0x8a, 0x9d, 0x88, 0x01, 0x71, 0x06, 0xce, 0xc1, 0x49, 0x3a, 0x76, 0x64, 0x02, 0x94, 0x5c, 0x04, - 0xc5, 0x49, 0x21, 0xdd, 0x9e, 0x3f, 0xff, 0xde, 0xfb, 0xfe, 0x40, 0x8b, 0xab, 0x98, 0xf2, 0x32, - 0xa5, 0xe5, 0x92, 0x46, 0x3c, 0xe3, 0x32, 0x91, 0x64, 0x9b, 0x0b, 0x25, 0x10, 0xe4, 0x2a, 0x26, - 0xbc, 0x4c, 0x49, 0xb9, 0xb4, 0xa7, 0x3d, 0xaa, 0x29, 0x69, 0xc2, 0x9e, 0x46, 0x22, 0x12, 0x5a, - 0xd2, 0x46, 0xb5, 0xd5, 0xc5, 0x1b, 0x3c, 0x7b, 0x68, 0x83, 0x36, 0x8a, 0x29, 0x8e, 0xd6, 0xf0, - 0x94, 0x05, 0x81, 0x28, 0x32, 0x25, 0x2d, 0x30, 0x1f, 0x38, 0x93, 0x95, 0x4d, 0xfe, 0xa3, 0x49, - 0xc7, 0xde, 0xb5, 0x88, 0x3b, 0xdc, 0x7d, 0xcd, 0x0c, 0xef, 0xcf, 0x81, 0xae, 0xe1, 0x68, 0xcb, - 0x72, 0x96, 0x4a, 0xeb, 0x64, 0x0e, 0x9c, 0xc9, 0x0a, 0xf5, 0xbd, 0x4f, 0xfa, 0xa7, 0xf3, 0x74, - 0xdc, 0xe2, 0x15, 0x9e, 0x1f, 0x67, 0x22, 0x0b, 0x9a, 0x2c, 0x0c, 0x73, 0x2e, 0x9b, 0x01, 0x80, - 0x33, 0xf6, 0x0e, 0x4f, 0x84, 0xe0, 0x30, 0x10, 0x21, 0xd7, 0xd9, 0x63, 0x4f, 0x6b, 0xb4, 0x86, - 0xa6, 0x54, 0x22, 0x67, 0x11, 0xb7, 0x06, 0x7a, 0xdc, 0xcb, 0x7e, 0x4b, 0xbd, 0x93, 0x7b, 0xd1, - 0x74, 0xfc, 0xf8, 0x9e, 0x99, 0x9b, 0x96, 0xf4, 0x0e, 0x16, 0xf7, 0x76, 0x57, 0x61, 0xb0, 0xaf, - 0x30, 0xf8, 0xa9, 0x30, 0x78, 0xaf, 0xb1, 0xb1, 0xaf, 0xb1, 0xf1, 0x59, 0x63, 0xe3, 0xf9, 0x2a, - 0x4a, 0x54, 0x5c, 0xf8, 0x24, 0x10, 0x29, 0x7d, 0x4c, 0xfc, 0x24, 0x2f, 0xee, 0x63, 0x96, 0x64, - 0x34, 0xd3, 0x9a, 0xbe, 0x34, 0x87, 0xf5, 0x47, 0xfa, 0x86, 0x37, 0xbf, 0x01, 0x00, 0x00, 0xff, - 0xff, 0x58, 0x7a, 0xad, 0xd9, 0x97, 0x01, 0x00, 0x00, + // 333 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x91, 0x31, 0x4e, 0xc3, 0x30, + 0x14, 0x86, 0x63, 0x5a, 0xb5, 0xd4, 0x45, 0x40, 0xad, 0x0e, 0x51, 0x87, 0xb4, 0x2a, 0x4b, 0xa7, + 0x98, 0x96, 0xb5, 0x0c, 0x14, 0x89, 0x4e, 0x20, 0x94, 0x32, 0xb1, 0x20, 0x37, 0x35, 0x49, 0x84, + 0x62, 0x47, 0xb1, 0x13, 0x31, 0x70, 0x08, 0xce, 0xc1, 0x45, 0xe8, 0xd8, 0x91, 0x09, 0x50, 0x7b, + 0x11, 0x64, 0xc7, 0x81, 0xb0, 0xfd, 0x7e, 0xfe, 0xbf, 0xf7, 0x3f, 0xfb, 0x41, 0x9b, 0xca, 0x10, + 0xd3, 0x3c, 0xc6, 0xf9, 0x18, 0x07, 0x94, 0x51, 0x11, 0x09, 0x37, 0x49, 0xb9, 0xe4, 0x08, 0x52, + 0x19, 0xba, 0x34, 0x8f, 0xdd, 0x7c, 0xdc, 0xeb, 0x56, 0x5c, 0xaa, 0xa4, 0x1d, 0xbd, 0x6e, 0xc0, + 0x03, 0xae, 0x25, 0x56, 0xaa, 0xa8, 0x0e, 0xdf, 0x01, 0x3c, 0x98, 0x17, 0x9d, 0x16, 0x92, 0x48, + 0x8a, 0xa6, 0x70, 0x9f, 0xf8, 0x3e, 0xcf, 0x98, 0x14, 0x36, 0x18, 0xd4, 0x46, 0xed, 0x49, 0xcf, + 0xfd, 0xeb, 0xed, 0x1a, 0xef, 0x45, 0x61, 0x99, 0xd5, 0xd7, 0x9f, 0x7d, 0xcb, 0xfb, 0x25, 0xd0, + 0x29, 0x6c, 0x24, 0x24, 0x25, 0xb1, 0xb0, 0xf7, 0x06, 0x60, 0xd4, 0x9e, 0xa0, 0x2a, 0x7b, 0xab, + 0x6f, 0x0c, 0x63, 0x7c, 0x68, 0x0e, 0x3b, 0x8f, 0x19, 0x93, 0xfc, 0x89, 0xb2, 0x87, 0x98, 0x24, + 0x49, 0xc4, 0x02, 0x61, 0xd7, 0x74, 0x70, 0xb7, 0x0a, 0x5f, 0x65, 0xec, 0x4e, 0x99, 0x0c, 0x7e, + 0x5c, 0x42, 0xd7, 0x86, 0x19, 0xbe, 0xc0, 0xc3, 0xff, 0xc3, 0x21, 0x1b, 0x36, 0xc9, 0x6a, 0x95, + 0x52, 0xa1, 0x5e, 0x02, 0x46, 0x2d, 0xaf, 0x3c, 0x22, 0x04, 0xeb, 0x3e, 0x5f, 0x51, 0x3d, 0x64, + 0xcb, 0xd3, 0x1a, 0x4d, 0x61, 0x53, 0x48, 0x9e, 0x92, 0x80, 0x9a, 0xf8, 0x4e, 0x35, 0x5e, 0x7f, + 0xce, 0xec, 0x48, 0x65, 0xbf, 0x7d, 0xf5, 0x9b, 0x8b, 0xc2, 0xe9, 0x95, 0xc8, 0xec, 0x7c, 0xbd, + 0x75, 0xc0, 0x66, 0xeb, 0x80, 0xef, 0xad, 0x03, 0x5e, 0x77, 0x8e, 0xb5, 0xd9, 0x39, 0xd6, 0xc7, + 0xce, 0xb1, 0xee, 0x4f, 0x82, 0x48, 0x86, 0xd9, 0xd2, 0xf5, 0x79, 0x8c, 0x6f, 0xa2, 0x65, 0x94, + 0x66, 0x97, 0x21, 0x89, 0x18, 0x66, 0x5a, 0xe3, 0x67, 0xb5, 0xa2, 0x65, 0x43, 0x6f, 0xe3, 0xec, + 0x27, 0x00, 0x00, 0xff, 0xff, 0x43, 0x29, 0x65, 0x1b, 0xe1, 0x01, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -194,6 +205,20 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.FuntokenMappings) > 0 { + for iNdEx := len(m.FuntokenMappings) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.FuntokenMappings[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } { size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -297,6 +322,12 @@ func (m *GenesisState) Size() (n int) { } l = m.Params.Size() n += 1 + l + sovGenesis(uint64(l)) + if len(m.FuntokenMappings) > 0 { + for _, e := range m.FuntokenMappings { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -425,6 +456,40 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FuntokenMappings", 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.FuntokenMappings = append(m.FuntokenMappings, FunToken{}) + if err := m.FuntokenMappings[len(m.FuntokenMappings)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/evm/keeper/evm_state.go b/x/evm/keeper/evm_state.go index 715105978..0f0013f1c 100644 --- a/x/evm/keeper/evm_state.go +++ b/x/evm/keeper/evm_state.go @@ -128,7 +128,7 @@ func (k Keeper) SetParams(ctx sdk.Context, params evm.Params) { k.EvmState.ModuleParams.Set(ctx, params) } -// SetState update contract storage, delete if value is empty. +// SetState updates contract storage and deletes if the value is empty. func (state EvmState) SetAccState( ctx sdk.Context, addr eth.EthAddr, stateKey eth.EthHash, stateValue []byte, ) { diff --git a/x/evm/keeper/funtoken_state.go b/x/evm/keeper/funtoken_state.go new file mode 100644 index 000000000..c713c1fc3 --- /dev/null +++ b/x/evm/keeper/funtoken_state.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package keeper + +import ( + "github.com/NibiruChain/collections" + sdkcodec "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/evm" + funtoken "github.com/NibiruChain/nibiru/x/evm" +) + +type ( + funtokenPrimaryKeyType = []byte + funtokenValueType = funtoken.FunToken +) + +// FunTokenState isolates the key-value stores (collections) for fungible token +// mappings. This struct is written as an extension of the default indexed map to +// add utility functions. +type FunTokenState struct { + collections.IndexedMap[funtokenPrimaryKeyType, funtokenValueType, IndexesFunToken] +} + +func NewFunTokenState( + cdc sdkcodec.BinaryCodec, + storeKey storetypes.StoreKey, +) FunTokenState { + primaryKeyEncoder := eth.KeyEncoderBytes + valueEncoder := collections.ProtoValueEncoder[funtokenValueType](cdc) + idxMap := collections.NewIndexedMap( + storeKey, evm.KeyPrefixFunTokens, primaryKeyEncoder, valueEncoder, + IndexesFunToken{ + ERC20Addr: collections.NewMultiIndex( + storeKey, evm.KeyPrefixFunTokenIdxErc20, + eth.KeyEncoderEthAddr, // indexing key (IK): ERC-20 addr + primaryKeyEncoder, + func(v evm.FunToken) gethcommon.Address { + return v.Erc20Addr.ToAddr() + }, + ), + BankDenom: collections.NewMultiIndex( + storeKey, evm.KeyPrefixFunTokenIdxBankDenom, + collections.StringKeyEncoder, // indexing key (IK): Coin denom + primaryKeyEncoder, + func(v evm.FunToken) string { return v.BankDenom }, + ), + }, + ) + return FunTokenState{IndexedMap: idxMap} +} + +func (idxs IndexesFunToken) IndexerList() []collections.Indexer[funtokenPrimaryKeyType, funtokenValueType] { + return []collections.Indexer[funtokenPrimaryKeyType, funtokenValueType]{ + idxs.ERC20Addr, + idxs.BankDenom, + } +} + +// IndexesFunToken: Abstraction for indexing over the FunToken store. +type IndexesFunToken struct { + // ERC20Addr (MultiIndex): Index FunToken by ERC-20 contract address. + // - indexing key (IK): ERC-20 addr + // - primary key (PK): FunToken ID + // - value (V): FunToken value + ERC20Addr collections.MultiIndex[gethcommon.Address, funtokenPrimaryKeyType, funtokenValueType] + + // BankDenom (MultiIndex): Index FunToken by coin denomination + // - indexing key (IK): Coin denom + // - primary key (PK): FunToken ID + // - value (V): FunToken value + BankDenom collections.MultiIndex[string, funtokenPrimaryKeyType, funtokenValueType] +} + +// Insert adds an [evm.FunToken] to state with defensive validation. Errors if +// the given inputs would result in a corrupted [evm.FunToken]. +func (fun FunTokenState) SafeInsert( + ctx sdk.Context, erc20 gethcommon.Address, bankDenom string, isMadeFromCoin bool, +) error { + funtoken := evm.NewFunToken(erc20, bankDenom, isMadeFromCoin) + if err := funtoken.Validate(); err != nil { + return err + } + fun.Insert(ctx, funtoken.ID(), funtoken) + return nil +} diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 41c26113b..e809b7577 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -32,6 +32,10 @@ type Keeper struct { // EvmState isolates the key-value stores (collections) for the x/evm module. EvmState EvmState + // FunTokens isolates the key-value stores (collections) for fungible token + // mappings. + FunTokens FunTokenState + // the address capable of executing a MsgUpdateParams message. Typically, this should be the x/gov module account. authority sdk.AccAddress @@ -49,6 +53,8 @@ type Keeper struct { tracer string } +// NewKeeper is a constructor for an x/evm [Keeper]. This function is necessary +// because the [Keeper] struct has private fields. func NewKeeper( cdc codec.BinaryCodec, storeKey, transientKey storetypes.StoreKey, @@ -67,6 +73,7 @@ func NewKeeper( transientKey: transientKey, authority: authority, EvmState: NewEvmState(cdc, storeKey, transientKey), + FunTokens: NewFunTokenState(cdc, storeKey), accountKeeper: accKeeper, bankKeeper: bankKeeper, stakingKeeper: stakingKeeper, diff --git a/x/evm/query_test.go b/x/evm/query_test.go deleted file mode 100644 index 7c6e54992..000000000 --- a/x/evm/query_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2023-2024 Nibi, Inc. -package evm - -import ( - "github.com/stretchr/testify/suite" - - "github.com/NibiruChain/nibiru/x/common" -) - -type TestSuite struct { - suite.Suite -} - -// TestNilQueries: Checks that all expected sad paths for nil msgs error -func (s *TestSuite) TestNilQueries() { - for _, testCase := range []func() error{ - func() error { - var req *QueryEthAccountRequest = nil - return req.Validate() - }, - func() error { - var req *QueryNibiruAccountRequest = nil - return req.Validate() - }, - func() error { - var req *QueryValidatorAccountRequest = nil - _, err := req.Validate() - return err - }, - func() error { - var req *QueryBalanceRequest = nil - return req.Validate() - }, - func() error { - var req *QueryStorageRequest = nil - return req.Validate() - }, - func() error { - var req *QueryCodeRequest = nil - return req.Validate() - }, - func() error { - var req *EthCallRequest = nil - return req.Validate() - }, - func() error { - var req *QueryTraceTxRequest = nil - return req.Validate() - }, - func() error { - var req *QueryTraceBlockRequest = nil - return req.Validate() - }, - } { - err := testCase() - s.Require().ErrorContains(err, common.ErrNilGrpcMsg.Error()) - } -}