├── .dockerignore ├── .gitattributes ├── .github ├── CODEOWNERS └── workflows │ ├── automated-release.yml │ ├── go.yml │ ├── integration-tests.yml │ ├── node.js.yml │ ├── proto.yml │ └── rust.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── Makefile ├── docs ├── architecture │ ├── README.md │ └── adr-template.md └── design │ ├── arbitrary-logic.md │ ├── overview.md │ └── relaying-semantics.md ├── ethereum └── Dockerfile ├── go.mod ├── go.sum ├── gravity-bridge.svg ├── module ├── .golangci.yml ├── .vscode │ └── launch.json ├── Dockerfile ├── Makefile ├── README.md ├── app │ ├── address.go │ ├── app.go │ ├── config.go │ ├── encoding.go │ ├── export.go │ ├── genesis.go │ ├── params │ │ ├── doc.go │ │ ├── encoding.go │ │ ├── params.go │ │ ├── proto.go │ │ └── weights.go │ ├── sim_test.go │ ├── state.go │ └── utils.go ├── buf.yaml ├── ci.Dockerfile ├── cmd │ └── gravity │ │ ├── cmd │ │ ├── eth_keys.go │ │ ├── genaccounts.go │ │ ├── genaccounts_test.go │ │ ├── gentx.go │ │ ├── root.go │ │ ├── root_test.go │ │ └── testnet.go │ │ └── main.go ├── contrib │ └── local │ │ ├── 01-bootstrap_attestation.sh │ │ ├── 02-fake_deposit_attestation.sh │ │ ├── 03-fake_withdraw_attestation.sh │ │ ├── 04-fake_multisig_attestation.sh │ │ ├── README.md │ │ ├── protocgen.sh │ │ ├── setup_node.sh │ │ └── start_node.sh ├── go.mod ├── go.sum ├── proto │ └── gravity │ │ └── v1 │ │ ├── genesis.proto │ │ ├── gravity.proto │ │ ├── msgs.proto │ │ └── query.proto ├── third_party │ └── proto │ │ ├── cosmos │ │ └── base │ │ │ ├── abci │ │ │ └── v1beta1 │ │ │ │ └── abci.proto │ │ │ ├── query │ │ │ └── v1beta1 │ │ │ │ └── pagination.proto │ │ │ └── v1beta1 │ │ │ └── coin.proto │ │ ├── cosmos_proto │ │ └── cosmos.proto │ │ ├── gogoproto │ │ └── gogo.proto │ │ ├── google │ │ ├── api │ │ │ ├── annotations.proto │ │ │ ├── http.proto │ │ │ └── httpbody.proto │ │ └── protobuf │ │ │ └── any.proto │ │ └── tendermint │ │ ├── abci │ │ └── types.proto │ │ ├── crypto │ │ ├── keys.proto │ │ └── proof.proto │ │ ├── libs │ │ └── bits │ │ │ └── types.proto │ │ ├── types │ │ ├── evidence.proto │ │ ├── params.proto │ │ └── types.proto │ │ └── version │ │ └── types.proto └── x │ ├── .gitkeep │ └── gravity │ ├── abci.go │ ├── abci_test.go │ ├── client │ └── cli │ │ ├── query.go │ │ └── tx.go │ ├── cosmos_originated_test.go │ ├── handler.go │ ├── handler_test.go │ ├── keeper │ ├── batch.go │ ├── batch_test.go │ ├── cosmos_originated.go │ ├── ethereum_event_handler.go │ ├── ethereum_event_handler_test.go │ ├── ethereum_event_vote.go │ ├── genesis.go │ ├── grpc_query.go │ ├── grpc_query_test.go │ ├── hooks.go │ ├── keeper.go │ ├── keeper_test.go │ ├── msg_server.go │ ├── msg_server_test.go │ ├── pool.go │ ├── pool_test.go │ └── test_common.go │ ├── module.go │ ├── spec │ ├── 01_definitions.md │ ├── 02_state.md │ ├── 03_state_transitions.md │ ├── 04_messages.md │ ├── 05_end_block.md │ ├── 06_events.md │ ├── 07_params.md │ └── README.md │ └── types │ ├── abi_json.go │ ├── checkpoint_test.go │ ├── codec.go │ ├── errors.go │ ├── ethereum.go │ ├── ethereum_event.go │ ├── ethereum_signature.go │ ├── ethereum_signer.go │ ├── ethereum_signer_test.go │ ├── events.go │ ├── expected_keepers.go │ ├── genesis.go │ ├── genesis.pb.go │ ├── genesis_test.go │ ├── gravity.pb.go │ ├── hooks.go │ ├── interfaces.go │ ├── key.go │ ├── msgs.go │ ├── msgs.pb.go │ ├── msgs_test.go │ ├── outgoing_tx.go │ ├── query.pb.go │ ├── types.go │ └── types_test.go ├── notes.md ├── orchestrator ├── .dockerignore ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── README.md ├── ci.Dockerfile ├── client │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── cosmos_gravity │ ├── Cargo.toml │ └── src │ │ ├── build.rs │ │ ├── lib.rs │ │ ├── query.rs │ │ ├── send.rs │ │ └── utils.rs ├── ethereum_gravity │ ├── Cargo.toml │ └── src │ │ ├── deploy_erc20.rs │ │ ├── lib.rs │ │ ├── logic_call.rs │ │ ├── send_to_cosmos.rs │ │ ├── submit_batch.rs │ │ ├── utils.rs │ │ └── valset_update.rs ├── gorc │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── gorc.toml │ ├── src │ │ ├── application.rs │ │ ├── bin │ │ │ └── gorc │ │ │ │ └── main.rs │ │ ├── commands.rs │ │ ├── commands │ │ │ ├── cosmos_to_eth.rs │ │ │ ├── deploy.rs │ │ │ ├── deploy │ │ │ │ └── erc20.rs │ │ │ ├── eth_to_cosmos.rs │ │ │ ├── keys.rs │ │ │ ├── keys │ │ │ │ ├── cosmos.rs │ │ │ │ ├── cosmos │ │ │ │ │ ├── add.rs │ │ │ │ │ ├── delete.rs │ │ │ │ │ ├── list.rs │ │ │ │ │ ├── recover.rs │ │ │ │ │ ├── rename.rs │ │ │ │ │ └── show.rs │ │ │ │ ├── eth.rs │ │ │ │ └── eth │ │ │ │ │ ├── add.rs │ │ │ │ │ ├── delete.rs │ │ │ │ │ ├── import.rs │ │ │ │ │ ├── list.rs │ │ │ │ │ ├── recover.rs │ │ │ │ │ ├── rename.rs │ │ │ │ │ └── show.rs │ │ │ ├── orchestrator.rs │ │ │ ├── orchestrator │ │ │ │ └── start.rs │ │ │ ├── print_config.rs │ │ │ ├── query.rs │ │ │ ├── query │ │ │ │ ├── cosmos.rs │ │ │ │ └── eth.rs │ │ │ ├── sign_delegate_keys.rs │ │ │ ├── tests.rs │ │ │ ├── tx.rs │ │ │ ├── tx │ │ │ │ ├── cosmos.rs │ │ │ │ └── eth.rs │ │ │ └── version.rs │ │ ├── config.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── prelude.rs │ │ └── utils.rs │ └── tests │ │ └── acceptance.rs ├── gravity_proto │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── prost │ │ ├── cosmos_proto.rs │ │ └── gravity.v1.rs ├── gravity_utils │ ├── Cargo.toml │ └── src │ │ ├── connection_prep.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── message_signatures.rs │ │ └── types │ │ ├── batches.rs │ │ ├── ethereum_events.rs │ │ ├── logic_call.rs │ │ ├── mod.rs │ │ ├── signatures.rs │ │ └── valsets.rs ├── orchestrator │ ├── Cargo.toml │ └── src │ │ ├── ethereum_event_watcher.rs │ │ ├── get_with_retry.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── main_loop.rs │ │ ├── metrics.rs │ │ └── oracle_resync.rs ├── proto_build │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── register_delegate_keys │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── relayer │ ├── Cargo.toml │ └── src │ │ ├── batch_relaying.rs │ │ ├── find_latest_valset.rs │ │ ├── lib.rs │ │ ├── logic_call_relaying.rs │ │ ├── main.rs │ │ ├── main_loop.rs │ │ └── valset_relaying.rs ├── startup.sh ├── test_runner │ ├── Cargo.toml │ ├── src │ │ ├── arbitrary_logic.rs │ │ ├── bootstrapping.rs │ │ ├── happy_path.rs │ │ ├── happy_path_v2.rs │ │ ├── main.rs │ │ ├── orch_keys_update.rs │ │ ├── transaction_stress_test.rs │ │ ├── utils.rs │ │ └── valset_stress.rs │ └── startup.sh └── testnet.Dockerfile ├── package-lock.json ├── readme.md ├── solidity ├── .dockerignore ├── Dockerfile ├── ci.Dockerfile ├── contract-deployer.ts ├── contracts │ ├── CosmosToken.sol │ ├── Gravity.sol │ ├── HashingTest.sol │ ├── ReentrantERC20.sol │ ├── SigningTest.sol │ ├── SimpleLogicBatch.sol │ ├── TestERC20A.sol │ ├── TestERC20B.sol │ ├── TestERC20C.sol │ ├── TestLogicContract.sol │ ├── TestTokenBatchMiddleware copy.sol │ ├── TestUniswapLiquidity.sol │ └── contract-explanation.md ├── debug-logs.ts ├── debugging.ts ├── gen │ ├── cosmos │ │ └── base │ │ │ ├── query │ │ │ └── v1beta1 │ │ │ │ └── pagination.ts │ │ │ └── v1beta1 │ │ │ └── coin.ts │ ├── cosmos_proto │ │ └── cosmos.ts │ ├── gogoproto │ │ └── gogo.ts │ ├── google │ │ ├── api │ │ │ ├── annotations.ts │ │ │ └── http.ts │ │ └── protobuf │ │ │ ├── any.ts │ │ │ ├── descriptor.ts │ │ │ └── timestamp.ts │ └── gravity │ │ └── v1 │ │ ├── genesis.ts │ │ ├── gravity.ts │ │ ├── msgs.ts │ │ └── query.ts ├── get-valset ├── hardhat.config.ts ├── how-to-setup-hardhat.txt ├── package-lock.json ├── package.json ├── possible_optimizations.md ├── protocgen.sh ├── scripts │ ├── contract-deployer.sh │ ├── get-valset.ts │ └── sample-script.js ├── test-utils │ ├── index.ts │ └── pure.ts ├── test │ ├── arbitrary-logic.ts │ ├── constructor.ts │ ├── deployERC20.ts │ ├── gas-test.ts │ ├── happy-path.ts │ ├── hashingTest.ts │ ├── sendToCosmos.ts │ ├── submitBatch-vs-logicCall.ts │ ├── submitBatch.ts │ └── updateValset.ts ├── tests_mainnet_fork │ └── uniswap-logic.ts └── tsconfig.json ├── spec ├── batch-creation-spec.md ├── slashing-spec.md └── valset-creation-spec.md └── testnet ├── README.md ├── chain.go ├── config.go ├── e2e_arbitrary_logic_test.go ├── e2e_batch_stress_test.go ├── e2e_happy_path_test.go ├── e2e_orchestrator_keys_test.go ├── e2e_v2_happy_path_test.go ├── e2e_validator_out_test.go ├── e2e_valset_stress_test.go ├── ethereum.go ├── genesis.go ├── go.mod ├── go.sum ├── main.go ├── orchestrator.go ├── testnet_test.go ├── validator.go └── with_pristine_e2e_environment_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | */node_modules 2 | */target/* 3 | .DS_Store 4 | .dockerignore 5 | .git 6 | .gitignore 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS: https://help.github.com/articles/about-codeowners/ 2 | 3 | module/ @jackzampolin @mvid @levicook 4 | orchestrator/ @zmanian @levicook @hannydevelop 5 | solidity/ @zmanian @jackzampolin @jtremback 6 | tests/ @mvid @levicook @jackzampolin 7 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | go-test: 11 | permissions: 12 | contents: read 13 | packages: write 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Install Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.16 20 | - name: Checkout Branch 21 | uses: actions/checkout@v2 22 | - name: Create Go cache 23 | uses: actions/cache@v2 24 | with: 25 | path: | 26 | ~/.cache/go-build 27 | ~/go/pkg/mod 28 | key: ${{ runner.os }}-go-${{ hashFiles('module/go.sum') }} 29 | - name: Run Go tests 30 | run: cd module && make test -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Solidity 5 | 6 | on: 7 | push: 8 | branches: [master, main] 9 | pull_request: 10 | branches: [master, main] 11 | 12 | env: 13 | REGISTRY: ghcr.io 14 | IMAGE_NAME: ${{ github.repository }} 15 | 16 | 17 | jobs: 18 | node-build: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | node-version: [ 16.x ] 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/cache@v2 26 | with: 27 | path: ~/.npm 28 | key: ${{ runner.os }}-node-${{ hashFiles('solidity/package-lock.json') }} 29 | - uses: actions/setup-node@v1 30 | with: 31 | node-version: ${{ matrix.node-version }} 32 | - run: cd solidity && npm ci 33 | - run: cd solidity && npm run typechain 34 | - run: cd solidity && npm run evm & 35 | - run: cd solidity && npm run test 36 | 37 | arbitrary-logic-tests: 38 | runs-on: ubuntu-latest 39 | strategy: 40 | matrix: 41 | node-version: [16.x] 42 | steps: 43 | - uses: actions/cache@v2 44 | with: 45 | path: ~/.npm 46 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 47 | restore-keys: | 48 | ${{ runner.os }}-node- 49 | - uses: actions/checkout@v2 50 | - name: Test arbitrary logic on mainnet fork 51 | uses: actions/setup-node@v1 52 | with: 53 | node-version: ${{ matrix.node-version }} 54 | - run: cd solidity && npm ci 55 | - run: cd solidity && npm run typechain 56 | - run: cd solidity && npm run evm_fork & 57 | env: 58 | ALCHEMY_ID: ${{ secrets.ALCHEMY_ID }} 59 | - run: cd solidity && npm run test_fork 60 | -------------------------------------------------------------------------------- /.github/workflows/proto.yml: -------------------------------------------------------------------------------- 1 | name: Protobuf 2 | # Protobuf runs buf (https://buf.build/) lint and check-breakage 3 | # This workflow is only run when a .proto file has been changed 4 | on: 5 | pull_request: 6 | 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-latest 10 | timeout-minutes: 5 11 | steps: 12 | - uses: actions/checkout@master 13 | - uses: technote-space/get-diff-action@v4 14 | with: 15 | PATTERNS: | 16 | **/**.proto 17 | - name: lint 18 | run: cd module && make proto-lint 19 | if: env.GIT_DIFF 20 | 21 | breakage: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@master 25 | - uses: technote-space/get-diff-action@v4 26 | with: 27 | PATTERNS: | 28 | **/**.proto 29 | - name: check-breakage 30 | run: cd module && make proto-check-breaking 31 | if: env.GIT_DIFF 32 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | rust-test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout branch 17 | uses: actions/checkout@v2 18 | - name: Set up Rust caches 19 | uses: actions/cache@v2 20 | id: rust-cache 21 | with: 22 | path: | 23 | ~/.cargo/bin/ 24 | ~/.cargo/registry/index/ 25 | ~/.cargo/registry/cache/ 26 | ~/.cargo/git/db/ 27 | orchestrator/target/ 28 | key: ${{ runner.os }}-cargo-${{ hashFiles('orchestrator/Cargo.lock') }} 29 | - name: Run Orchestrator unit tests 30 | run: cd orchestrator && cargo test --all --verbose -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | typechain 3 | target 4 | tests/test-runner/Cargo.lock 5 | tests/dockerfile/gravity.tar.gz 6 | solidity/dist 7 | testdata/ 8 | orchestrator/bins 9 | out/ 10 | 11 | # IDE files 12 | .idea/ 13 | 14 | #Hardhat files 15 | cache 16 | artifacts 17 | *.test 18 | 19 | .DS_Store 20 | 21 | build/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 100, 7 | "tabWidth": 4, 8 | "useTabs": true, 9 | "singleQuote": false, 10 | "bracketSpacing": true, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /docs/architecture/README.md: -------------------------------------------------------------------------------- 1 | # Architecture Decision Records (ADR) 2 | 3 | This is a location to record all high-level architecture decisions For the Gravity Bridge. 4 | 5 | An Architectural Decision (**AD**) is a software design choice that addresses a functional or non-functional requirement that is architecturally significant. 6 | An Architecturally Significant Requirement (**ASR**) is a requirement that has a measurable effect on a software system’s architecture and quality. 7 | An Architectural Decision Record (**ADR**) captures a single AD, such as often done when writing personal notes or meeting minutes; the collection of ADRs created and maintained in a project constitute its decision log. All these are within the topic of Architectural Knowledge Management (AKM). 8 | 9 | You can read more about the ADR concept in this [blog post](https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0#.78xhdix6t). 10 | 11 | ## Rationale 12 | 13 | ADRs are intended to be the primary mechanism for proposing new feature designs and new processes, for collecting community input on an issue, and for documenting the design decisions. 14 | An ADR should provide: 15 | 16 | - Context on the relevant goals and the current state 17 | - Proposed changes to achieve the goals 18 | - Summary of pros and cons 19 | - References 20 | - Changelog 21 | 22 | Note the distinction between an ADR and a spec. The ADR provides the context, intuition, reasoning, and 23 | justification for a change in architecture, or for the architecture of something 24 | new. The spec is much more compressed and streamlined summary of everything as 25 | it stands today. 26 | 27 | If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match. 28 | 29 | ## ADR Table of Contents 30 | 31 | ### Accepted 32 | 33 | ### Proposed 34 | -------------------------------------------------------------------------------- /docs/architecture/adr-template.md: -------------------------------------------------------------------------------- 1 | # ADR {ADR-NUMBER}: {TITLE} 2 | 3 | ## Changelog 4 | 5 | - {date}: {changelog} 6 | 7 | ## Status 8 | 9 | {DRAFT | PROPOSED} Not Implemented 10 | 11 | > Please have a look at the [PROCESS](./PROCESS.md#adr-status) page. 12 | > Use DRAFT if the ADR is in a draft stage (draft PR) or PROPOSED if it's in review. 13 | 14 | ## Abstract 15 | 16 | > "If you can't explain it simply, you don't understand it well enough." Provide a simplified and layman-accessible explanation of the ADR. 17 | > A short (~200 word) description of the issue being addressed. 18 | 19 | ## Context 20 | 21 | > This section describes the forces at play, including technological, political, social, and project local. These forces are probably in tension, and should be called out as such. The language in this section is value-neutral. It is simply describing facts. It should clearly explain the problem and motivation that the proposal aims to resolve. 22 | > {context body} 23 | 24 | ## Decision 25 | 26 | > This section describes our response to these forces. It is stated in full sentences, with active voice. "We will ..." 27 | > {decision body} 28 | 29 | ## Consequences 30 | 31 | > This section describes the resulting context, after applying the decision. All consequences should be listed here, not just the "positive" ones. A particular decision may have positive, negative, and neutral consequences, but all of them affect the team and project in the future. 32 | 33 | ### Backwards Compatibility 34 | 35 | > All ADRs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The ADR must explain how the author proposes to deal with these incompatibilities. ADR submissions without a sufficient backwards compatibility treatise may be rejected outright. 36 | 37 | ### Positive 38 | 39 | {positive consequences} 40 | 41 | ### Negative 42 | 43 | {negative consequences} 44 | 45 | ### Neutral 46 | 47 | {neutral consequences} 48 | 49 | ## Further Discussions 50 | 51 | While an ADR is in the DRAFT or PROPOSED stage, this section should contain a summary of issues to be solved in future iterations (usually referencing comments from a pull-request discussion). 52 | Later, this section can optionally list ideas or improvements the author or reviewers found during the analysis of this ADR. 53 | 54 | ## Test Cases [optional] 55 | 56 | Test cases for an implementation are mandatory for ADRs that are affecting consensus changes. Other ADRs can choose to include links to test cases if applicable. 57 | 58 | ## References 59 | 60 | - {reference link} 61 | -------------------------------------------------------------------------------- /ethereum/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ethereum/client-go:v1.10.3 2 | 3 | RUN apk add --no-cache curl 4 | 5 | COPY testdata/testchain/ETHGenesis.json ETHGenesis.json 6 | 7 | RUN geth --identity "GravityTestnet" \ 8 | --nodiscover \ 9 | --networkid 15 init ETHGenesis.json 10 | 11 | ENTRYPOINT geth --identity "GravityTestnet" --nodiscover \ 12 | --networkid 15 \ 13 | --mine \ 14 | --http \ 15 | --http.port "8545" \ 16 | --http.addr "0.0.0.0" \ 17 | --http.corsdomain "*" \ 18 | --http.vhosts "*" \ 19 | --miner.threads=1 \ 20 | --nousb \ 21 | --verbosity=3 \ 22 | --miner.etherbase=0xBf660843528035a5A4921534E156a27e64B231fE \ 23 | --rpc.allow-unprotected-txs -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cosmos/gravity-bridge 2 | 3 | go 1.15 4 | 5 | require github.com/cosmos/gravity-bridge/module v0.1.11 // indirect 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/gravity-bridge/0195030967c3fef8f86da44ee89caa99564304cf/go.sum -------------------------------------------------------------------------------- /module/.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - bodyclose 4 | - deadcode 5 | - depguard 6 | - dogsled 7 | # - dupl 8 | - errcheck 9 | # - funlen 10 | # - gochecknoglobals 11 | # - gochecknoinits 12 | - goconst 13 | - gocritic 14 | # - gocyclo 15 | # - godox 16 | - gofmt 17 | - goimports 18 | - golint 19 | - gosec 20 | - gosimple 21 | - govet 22 | - ineffassign 23 | - lll 24 | - misspell 25 | # - maligned 26 | - nakedret 27 | - prealloc 28 | - scopelint 29 | - staticcheck 30 | - structcheck 31 | - stylecheck 32 | - typecheck 33 | - unconvert 34 | # - unparam 35 | - unused 36 | - varcheck 37 | # - whitespace 38 | # - wsl 39 | # - gocognit 40 | 41 | linters-settings: 42 | govet: 43 | check-shadowing: true 44 | errcheck: 45 | # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; 46 | # default is false: such cases aren't reported by default. 47 | check-blank: true 48 | golint: 49 | # minimal confidence for issues, default is 0.8 50 | min-confidence: 0 51 | prealloc: 52 | # XXX: we don't recommend using this linter before doing performance profiling. 53 | # For most programs usage of prealloc will be a premature optimization. 54 | 55 | # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. 56 | # True by default. 57 | simple: false 58 | range-loops: true # Report preallocation suggestions on range loops, true by default 59 | for-loops: true # Report preallocation suggestions on for loops, false by default 60 | -------------------------------------------------------------------------------- /module/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch gravityd", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "exec", 12 | "program": "/go/bin/gravityd", 13 | "showLog": true, 14 | "args": [ 15 | "--home", 16 | "/validator1", 17 | "--address", 18 | "tcp://7.7.7.1:26655", 19 | "--rpc.laddr", 20 | "tcp://7.7.7.1:26657", 21 | "--p2p.laddr", 22 | "tcp://7.7.7.1:26656", 23 | "--log_level", 24 | "info", 25 | "start" 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /module/Dockerfile: -------------------------------------------------------------------------------- 1 | # Simple usage with a mounted data directory: 2 | # > docker build -t simapp . 3 | # 4 | # Server: 5 | # > docker run -it -p 26657:26657 -p 26656:26656 -v ~/.simapp:/root/.simapp simapp gravity init test-chain 6 | # TODO: need to set validator in genesis so start runs 7 | # > docker run -it -p 26657:26657 -p 26656:26656 -v ~/.simapp:/root/.simapp simapp gravity start 8 | # 9 | # Client: (Note the simapp binary always looks at ~/.simapp we can bind to different local storage) 10 | # > docker run -it -p 26657:26657 -p 26656:26656 -v ~/.simappcli:/root/.simapp simapp gravity keys add foo 11 | # > docker run -it -p 26657:26657 -p 26656:26656 -v ~/.simappcli:/root/.simapp simapp gravity keys list 12 | # TODO: demo connecting rest-server (or is this in server now?) 13 | FROM golang:alpine AS build-env 14 | 15 | # Install minimum necessary dependencies, 16 | ENV PACKAGES curl make git libc-dev bash gcc linux-headers eudev-dev python3 17 | RUN apk add --no-cache $PACKAGES 18 | 19 | # Set working directory for the build 20 | WORKDIR /go/src/github.com/althea-net/cosmos-gravity-bridge/module 21 | 22 | # Get dependancies - will also be cached if we won't change mod/sum 23 | COPY go.mod . 24 | COPY go.sum . 25 | RUN go mod download 26 | 27 | # Add source files 28 | COPY . . 29 | 30 | # install simapp, remove packages 31 | RUN go build -o build/gravity ./cmd/gravity/main.go 32 | 33 | # Final image 34 | FROM alpine:edge 35 | 36 | # Install ca-certificates 37 | RUN apk add bash 38 | RUN apk add --update ca-certificates 39 | WORKDIR /root 40 | 41 | # Copy over binaries from the build-env 42 | COPY --from=build-env /go/src/github.com/althea-net/cosmos-gravity-bridge/module/build/gravity /usr/bin/gravity 43 | 44 | EXPOSE 26656 26657 1317 9090 45 | 46 | # Run gravity by default 47 | CMD ["gravity", "--home", "home", "start", "--pruning=nothing"] -------------------------------------------------------------------------------- /module/README.md: -------------------------------------------------------------------------------- 1 | ## Building 2 | 3 | On first run: 4 | make proto-update-deps 5 | make proto-tools 6 | To build: 7 | make 8 | 9 | ## Early MVP 10 | 11 | Happy path implementations 12 | 13 | ### Oracle 14 | 15 | #### Assumptions 16 | 17 | - An orchestrator may want to submit multiple claims with a msg (withdrawal batch update + MultiSig Set update ) 18 | - Nonces are not unique without a context (withdrawal nonce and MultiSig Set update can have same nonce (=height)) 19 | - A nonce is unique in it's context and never reused 20 | - Multiple claims by an orchestrator for the same ETH event are forbidden 21 | - We know the ETH event types beforehand (and handle them as ClaimTypes) 22 | - For an **observation** status in Attestation the power AND count thresholds must be exceeded 23 | - Fraction type allows higher precision math than %. For example with 2/3 24 | 25 | A good start to follow the process would be the `x/gravity/handler_test.go` file 26 | 27 | ### Outgoing TX Pool 28 | 29 | #### Features 30 | 31 | - Unique denominator for gravity vouchers in cosmos (🚧 cut to 15 chars and without a separator due to sdk limitations in v0.38.4) 32 | - Voucher burning 🔥 (minting in test ⛏️ ) 33 | - Store/ resolve bridged ETH denominator and contract 34 | - Persistent transaction pool 35 | - Transactions sorted by fees (on a second index) 36 | - Extended test setup 37 | 38 | #### Assumptions 39 | 40 | - We have only 1 chainID and 1 ETH contract 41 | 42 | ### Bundle Outgoing TX into Batches 43 | 44 | #### Features 45 | 46 | - `BatchTx` type with `OutgoingTransferTx` and `TransferCoin` 47 | - Logic to build batch from pending TXs based on fee desc order 48 | - Logic to cancel a batch and revert TXs back to pending pool 49 | - Incremental and unique IDs for batches to be used for `nonces` 50 | - `VoucherDenom` as first class type 51 | 52 | ## Not covered/ implemented 53 | 54 | - [ ] unhappy cases 55 | - [ ] proper unit + integration tests 56 | - [ ] message validation 57 | - [ ] Genesis I/O 58 | - [ ] Parameters 59 | - [ ] authZ: EthereumChainID whitelisted 60 | - [ ] authZ: bridge contract address whitelisted 61 | -------------------------------------------------------------------------------- /module/app/address.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | // SetAddressConfig sets the gravity app's address configuration. 8 | func SetAddressConfig() { 9 | config := sdk.GetConfig() 10 | 11 | config.SetAddressVerifier(VerifyAddressFormat) 12 | config.Seal() 13 | } 14 | -------------------------------------------------------------------------------- /module/app/encoding.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/std" 5 | gravityparams "github.com/cosmos/gravity-bridge/module/app/params" 6 | ) 7 | 8 | // MakeEncodingConfig creates an EncodingConfig for gravity. 9 | func MakeEncodingConfig() gravityparams.EncodingConfig { 10 | encodingConfig := gravityparams.MakeEncodingConfig() 11 | std.RegisterLegacyAminoCodec(encodingConfig.Amino) 12 | std.RegisterInterfaces(encodingConfig.InterfaceRegistry) 13 | ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) 14 | ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) 15 | return encodingConfig 16 | } 17 | -------------------------------------------------------------------------------- /module/app/genesis.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // The genesis state of the blockchain is represented here as a map of raw json 8 | // messages key'd by a identifier string. 9 | // The identifier is used to determine which module genesis information belongs 10 | // to so it may be appropriately routed during init chain. 11 | // Within this application default genesis information is retrieved from 12 | // the ModuleBasicManager which populates json from each BasicModule 13 | // object provided to it during init. 14 | type GenesisState map[string]json.RawMessage 15 | 16 | // NewDefaultGenesisState generates the default state for the application. 17 | func NewDefaultGenesisState() GenesisState { 18 | encCfg := MakeEncodingConfig() 19 | return ModuleBasics.DefaultGenesis(encCfg.Marshaler) 20 | } 21 | -------------------------------------------------------------------------------- /module/app/params/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package params defines the simulation parameters in the simapp. 3 | 4 | It contains the default weights used for each transaction used on the module's 5 | simulation. These weights define the chance for a transaction to be simulated at 6 | any gived operation. 7 | 8 | You can repace the default values for the weights by providing a params.json 9 | file with the weights defined for each of the transaction operations: 10 | 11 | { 12 | "op_weight_msg_send": 60, 13 | "op_weight_msg_delegate": 100, 14 | } 15 | 16 | In the example above, the `MsgSend` has 60% chance to be simulated, while the 17 | `MsgDelegate` will always be simulated. 18 | */ 19 | package params 20 | -------------------------------------------------------------------------------- /module/app/params/encoding.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/client" 5 | "github.com/cosmos/cosmos-sdk/codec" 6 | "github.com/cosmos/cosmos-sdk/codec/types" 7 | ) 8 | 9 | // EncodingConfig specifies the concrete encoding types to use for a given app. 10 | // This is provided for compatibility between protobuf and amino implementations. 11 | type EncodingConfig struct { 12 | InterfaceRegistry types.InterfaceRegistry 13 | Marshaler codec.Codec 14 | TxConfig client.TxConfig 15 | Amino *codec.LegacyAmino 16 | } 17 | -------------------------------------------------------------------------------- /module/app/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Simulation parameter constants 4 | const ( 5 | StakePerAccount = "stake_per_account" 6 | InitiallyBondedValidators = "initially_bonded_validators" 7 | ) 8 | -------------------------------------------------------------------------------- /module/app/params/proto.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/types" 6 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 7 | ) 8 | 9 | // MakeEncodingConfig creates an EncodingConfig for an amino based test configuration. 10 | func MakeEncodingConfig() EncodingConfig { 11 | amino := codec.NewLegacyAmino() 12 | interfaceRegistry := types.NewInterfaceRegistry() 13 | marshaler := codec.NewProtoCodec(interfaceRegistry) 14 | txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes) 15 | 16 | return EncodingConfig{ 17 | InterfaceRegistry: interfaceRegistry, 18 | Marshaler: marshaler, 19 | TxConfig: txCfg, 20 | Amino: amino, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /module/app/params/weights.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Default simulation operation weights for messages and gov proposals 4 | const ( 5 | DefaultWeightMsgSend int = 100 6 | DefaultWeightMsgMultiSend int = 10 7 | DefaultWeightMsgSetWithdrawAddress int = 50 8 | DefaultWeightMsgWithdrawDelegationReward int = 50 9 | DefaultWeightMsgWithdrawValidatorCommission int = 50 10 | DefaultWeightMsgFundCommunityPool int = 50 11 | DefaultWeightMsgDeposit int = 100 12 | DefaultWeightMsgVote int = 67 13 | DefaultWeightMsgUnjail int = 100 14 | DefaultWeightMsgCreateValidator int = 100 15 | DefaultWeightMsgEditValidator int = 5 16 | DefaultWeightMsgDelegate int = 100 17 | DefaultWeightMsgUndelegate int = 100 18 | DefaultWeightMsgBeginRedelegate int = 100 19 | 20 | DefaultWeightCommunitySpendProposal int = 5 21 | DefaultWeightTextProposal int = 5 22 | DefaultWeightParamChangeProposal int = 5 23 | ) 24 | -------------------------------------------------------------------------------- /module/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | 3 | build: 4 | roots: 5 | - proto 6 | - third_party/proto 7 | excludes: 8 | - third_party/proto/google/protobuf 9 | lint: 10 | use: 11 | - DEFAULT 12 | - COMMENTS 13 | - FILE_LOWER_SNAKE_CASE 14 | except: 15 | - UNARY_RPC 16 | - COMMENT_FIELD 17 | - COMMENT_ENUM 18 | - COMMENT_ENUM_VALUE 19 | - COMMENT_RPC 20 | - COMMENT_MESSAGE 21 | - SERVICE_SUFFIX 22 | - PACKAGE_VERSION_SUFFIX 23 | - RPC_REQUEST_STANDARD_NAME 24 | - RPC_RESPONSE_STANDARD_NAME 25 | - RPC_REQUEST_RESPONSE_UNIQUE 26 | ignore: 27 | - tendermint 28 | - gogoproto 29 | - cosmos_proto 30 | - google 31 | breaking: 32 | use: 33 | - FILE 34 | except: 35 | - FIELD_NO_DELETE 36 | ignore: 37 | - tendermint 38 | - gogoproto 39 | - cosmos_proto 40 | - google 41 | -------------------------------------------------------------------------------- /module/ci.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | # Install ca-certificates 4 | RUN apt get install ca-certificates 5 | 6 | EXPOSE 26656 26657 1317 9090 7 | 8 | COPY build/gravity /usr/bin/gravity 9 | 10 | CMD ["gravity", "start"] -------------------------------------------------------------------------------- /module/cmd/gravity/cmd/genaccounts_test.go: -------------------------------------------------------------------------------- 1 | package cmd_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/spf13/viper" 9 | "github.com/stretchr/testify/require" 10 | "github.com/tendermint/tendermint/libs/log" 11 | 12 | "github.com/cosmos/cosmos-sdk/client" 13 | "github.com/cosmos/cosmos-sdk/client/flags" 14 | "github.com/cosmos/cosmos-sdk/server" 15 | "github.com/cosmos/cosmos-sdk/simapp" 16 | simcmd "github.com/cosmos/cosmos-sdk/simapp/simd/cmd" 17 | "github.com/cosmos/cosmos-sdk/testutil/testdata" 18 | "github.com/cosmos/cosmos-sdk/types/module" 19 | "github.com/cosmos/cosmos-sdk/x/genutil" 20 | genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" 21 | ) 22 | 23 | var testMbm = module.NewBasicManager(genutil.AppModuleBasic{}) 24 | 25 | func TestAddGenesisAccountCmd(t *testing.T) { 26 | _, _, addr1 := testdata.KeyTestPubAddr() 27 | tests := []struct { 28 | name string 29 | addr string 30 | denom string 31 | expectErr bool 32 | }{ 33 | { 34 | name: "invalid address", 35 | addr: "", 36 | denom: "1000atom", 37 | expectErr: true, 38 | }, 39 | { 40 | name: "valid address", 41 | addr: addr1.String(), 42 | denom: "1000atom", 43 | expectErr: false, 44 | }, 45 | { 46 | name: "multiple denoms", 47 | addr: addr1.String(), 48 | denom: "1000atom, 2000stake", 49 | expectErr: false, 50 | }, 51 | } 52 | 53 | for _, tc := range tests { 54 | tc := tc 55 | t.Run(tc.name, func(t *testing.T) { 56 | home := t.TempDir() 57 | logger := log.NewNopLogger() 58 | cfg, err := genutiltest.CreateDefaultTendermintConfig(home) 59 | require.NoError(t, err) 60 | 61 | appCodec := simapp.MakeTestEncodingConfig().Marshaler 62 | err = genutiltest.ExecInitCmd(testMbm, home, appCodec) 63 | require.NoError(t, err) 64 | 65 | serverCtx := server.NewContext(viper.New(), cfg, logger) 66 | clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(home) 67 | 68 | ctx := context.Background() 69 | ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) 70 | ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) 71 | 72 | cmd := simcmd.AddGenesisAccountCmd(home) 73 | cmd.SetArgs([]string{ 74 | tc.addr, 75 | tc.denom, 76 | fmt.Sprintf("--%s=home", flags.FlagHome), 77 | }) 78 | 79 | if tc.expectErr { 80 | require.Error(t, cmd.ExecuteContext(ctx)) 81 | } else { 82 | require.NoError(t, cmd.ExecuteContext(ctx)) 83 | } 84 | }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /module/cmd/gravity/cmd/root_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/cosmos/cosmos-sdk/client/flags" 10 | "github.com/cosmos/cosmos-sdk/client/keys" 11 | "github.com/cosmos/cosmos-sdk/crypto/hd" 12 | "github.com/cosmos/cosmos-sdk/crypto/keyring" 13 | "github.com/stretchr/testify/require" 14 | "github.com/tendermint/tendermint/libs/cli" 15 | ) 16 | 17 | type KeyOutput struct { 18 | Name string `json:"name"` 19 | Type string `json:"type"` 20 | Address string `json:"address"` 21 | PubKey string `json:"pubkey"` 22 | } 23 | 24 | func TestKeyGen(t *testing.T) { 25 | mnemonic := "weasel lunch attack blossom tone drum unfair worry risk level negative height sight nation inside task oyster client shiver aware neck mansion gun dune" 26 | 27 | // generate key from binary 28 | keyCmd := keys.AddKeyCommand() 29 | keyCmd.Flags().String(cli.OutputFlag, "json", "output flag") 30 | keyCmd.Flags().String(flags.FlagKeyringBackend, keyring.BackendTest, "Select keyring's backend (os|file|kwallet|pass|test|memory)") 31 | keyCmd.SetArgs([]string{ 32 | "--dry-run=true", 33 | "--output=json", 34 | "--recover=true", 35 | "orch", 36 | }) 37 | keyCmd.SetIn(strings.NewReader(mnemonic + "\n")) 38 | 39 | buf := bytes.NewBuffer(nil) 40 | keyCmd.SetOut(buf) 41 | keyCmd.SetErr(buf) 42 | 43 | err := Execute(keyCmd) 44 | require.NoError(t, err) 45 | 46 | var key KeyOutput 47 | output := buf.Bytes() 48 | t.Log("outputs: ", string(output)) 49 | err = json.Unmarshal(output, &key) 50 | require.NoError(t, err) 51 | 52 | // generate a memory key directly 53 | kb, err := keyring.New("testnet", keyring.BackendMemory, "", nil) 54 | if err != nil { 55 | return 56 | } 57 | 58 | keyringAlgos, _ := kb.SupportedAlgorithms() 59 | algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyringAlgos) 60 | if err != nil { 61 | return 62 | } 63 | 64 | account, err := kb.NewAccount("", mnemonic, "", "m/44'/118'/0'/0/0", algo) 65 | require.NoError(t, err) 66 | 67 | require.Equal(t, account.GetAddress().String(), key.Address) 68 | } 69 | -------------------------------------------------------------------------------- /module/cmd/gravity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cosmos/cosmos-sdk/server" 7 | "github.com/cosmos/gravity-bridge/module/cmd/gravity/cmd" 8 | ) 9 | 10 | func main() { 11 | rootCmd, _ := cmd.NewRootCmd() 12 | if err := cmd.Execute(rootCmd); err != nil { 13 | switch e := err.(type) { 14 | case server.ErrorCode: 15 | os.Exit(e.Code) 16 | default: 17 | os.Exit(1) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /module/contrib/local/01-bootstrap_attestation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | echo "## Add ETH key" 5 | gravitycli tx gravity update-eth-addr 0xb8662f35f9de8720424e82b232e8c98d15399490adae9ca993f5ef1dc4883690 --from validator --chain-id=testing -b block -y 6 | 7 | echo "## Submit observation" 8 | nonce=$(date +%s) # use unix timestamp as fake nonce 9 | # chain id: 1 10 | # bridge contract address: 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf 11 | # erc20 token contract address: 0x7c2c195cd6d34b8f845992d380aadb2730bb9c6f 12 | # erc20 symbol: ALX 13 | # amount: 100 14 | # gravitycli tx gravity observed bootstrap [eth chain id] [eth contract address] [nonce] [allowed_validators] [validator_powers] [gravity_id] [start_threshold] 15 | gravitycli tx gravity observed bootstrap 1 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf "$nonce" "0xc783df8a850f42e7f7e57013759c285caa701eb6" "10" my-gravity-id 0 --from validator --chain-id=testing -b block -y 16 | 17 | echo "## View attestation status" 18 | gravitycli q gravity attestation bridge_bootstrap "$nonce" -o json | jq 19 | 20 | echo "## Query last observed state" 21 | gravitycli q gravity observed nonces -o json | jq 22 | -------------------------------------------------------------------------------- /module/contrib/local/02-fake_deposit_attestation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | echo "## Add ETH key" 5 | gravitycli tx gravity update-eth-addr 0xb8662f35f9de8720424e82b232e8c98d15399490adae9ca993f5ef1dc4883690 --from validator --chain-id=testing -b block -y 6 | 7 | echo "## Submit observation" 8 | nonce=$(date +%s) # use unix timestamp as fake nonce 9 | # chain id: 1 10 | # bridge contract address: 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf 11 | # erc20 token contract address: 0x7c2c195cd6d34b8f845992d380aadb2730bb9c6f 12 | # erc20 symbol: ALX 13 | # amount: 100 14 | # gravitycli tx gravity observed deposit [eth chain id] [eth contract address] [nonce] [cosmos receiver] [amount] [eth erc20 symbol] [eth erc20 contract addr] [eth sender address] [flags] 15 | gravitycli tx gravity observed deposit 1 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf "$nonce" $(gravitycli keys show validator -a) 1000 ALX 0xc783df8a850f42e7f7e57013759c285caa701eb6 0x7c2c195cd6d34b8f845992d380aadb2730bb9c6f --from validator --chain-id=testing -b block -y 16 | 17 | echo "## Query balance" 18 | gravitycli q account $(gravitycli keys show validator -a) 19 | echo "## Query last observed state" 20 | gravitycli q gravity observed nonces -o json -------------------------------------------------------------------------------- /module/contrib/local/03-fake_withdraw_attestation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux 3 | 4 | echo "--> Needs to run after deposit\n" 5 | echo "## Add ETH key" 6 | gravitycli tx gravity update-eth-addr 0xb8662f35f9de8720424e82b232e8c98d15399490adae9ca993f5ef1dc4883690 --from validator --chain-id=testing -b block -y 7 | 8 | echo "## Add ETH withdraw to pool" 9 | gravitycli tx gravity withdraw validator 0xc783df8a850f42e7f7e57013759c285caa701eb6 1gravityf01b315c8e 0gravityf01b315c8e --from validator --chain-id=testing -b block -y 10 | 11 | echo "## Request a batch for outgoing TX" 12 | gravitycli tx gravity build-batch gravityf01b315c8e --from validator --chain-id=testing -b block -y 13 | 14 | echo "## Query pending request nonce" 15 | nonce=$(gravitycli q gravity pending-batch-request $(gravitycli keys show validator -a) -o json | jq -r ".value.nonce") 16 | 17 | echo "## Approve pending request" 18 | gravitycli tx gravity approved batch-confirm "$nonce" 0xb8662f35f9de8720424e82b232e8c98d15399490adae9ca993f5ef1dc4883690 --from validator --chain-id=testing -b block -y 19 | 20 | echo "## Submit observation" 21 | # chain id: 1 22 | # bridge contract address: 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf 23 | gravitycli tx gravity observed withdrawal 1 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf "$nonce" --from validator --chain-id=testing -b block -y 24 | 25 | echo "## Query balance" 26 | gravitycli q account $(gravitycli keys show validator -a) 27 | echo "## Query last observed state" 28 | gravitycli q gravity observed nonces -o json -------------------------------------------------------------------------------- /module/contrib/local/04-fake_multisig_attestation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | echo "## Add ETH key" 5 | gravitycli tx gravity update-eth-addr 0xb8662f35f9de8720424e82b232e8c98d15399490adae9ca993f5ef1dc4883690 --from validator --chain-id=testing -b block -y 6 | echo "## Request valset update" 7 | gravitycli tx gravity valset-request --from validator --chain-id=testing -b block -y 8 | echo "## Query pending request nonce" 9 | nonce=$(gravitycli q gravity pending-valset-request $(gravitycli keys show validator -a) -o json | jq -r ".value.nonce") 10 | 11 | echo "## Approve pending request" 12 | gravitycli tx gravity approved valset-confirm "$nonce" 0xb8662f35f9de8720424e82b232e8c98d15399490adae9ca993f5ef1dc4883690 --from validator --chain-id=testing -b block -y 13 | 14 | echo "## View attestations" 15 | gravitycli q gravity attestation orchestrator_signed_multisig_update $nonce -o json | jq 16 | 17 | echo "## Submit observation" 18 | # chain id: 1 19 | # bridge contract address: 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf 20 | #gravitycli tx gravity observed multisig-update 1 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf "$nonce" --from validator --chain-id=testing -b block -y 21 | echo "## Query last observed state" 22 | gravitycli q gravity observed nonces -o json -------------------------------------------------------------------------------- /module/contrib/local/README.md: -------------------------------------------------------------------------------- 1 | # Local development 2 | Collection of bash scripts to help with manual tests. Not for production. -------------------------------------------------------------------------------- /module/contrib/local/protocgen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | proto_dirs=$(find ./proto -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 6 | for dir in $proto_dirs; do 7 | buf protoc \ 8 | -I "proto" \ 9 | -I "third_party/proto" \ 10 | --gocosmos_out=plugins=interfacetype+grpc,\ 11 | Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types:. \ 12 | $(find "${dir}" -maxdepth 1 -name '*.proto') 13 | 14 | # # command to generate gRPC gateway (*.pb.gw.go in respective modules) files 15 | # buf protoc \ 16 | # -I "proto" \ 17 | # -I "third_party/proto" \ 18 | # --grpc-gateway_out=logtostderr=true:. \ 19 | # $(find "${dir}" -maxdepth 1 -name '*.proto') 20 | 21 | done 22 | 23 | # move proto files to the right places 24 | cp -r github.com/cosmos/gravity-bridge/module/* ./ 25 | rm -rf github.com 26 | -------------------------------------------------------------------------------- /module/contrib/local/setup_node.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | gravityd init --chain-id=testing local 5 | gravityd add-genesis-account validator 1000000000stake 6 | gravityd gentx --name validator --amount 1000000000stake 7 | gravityd collect-gentxs 8 | -------------------------------------------------------------------------------- /module/contrib/local/start_node.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | gravityd start --rpc.laddr tcp://0.0.0.0:26657 --trace --log_level="main:info,state:debug,*:error" -------------------------------------------------------------------------------- /module/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cosmos/gravity-bridge/module 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/cosmos/cosmos-sdk v0.43.0 7 | github.com/cosmos/ibc-go v1.0.1 8 | github.com/ethereum/go-ethereum v1.9.25 9 | github.com/gogo/protobuf v1.3.3 10 | github.com/gorilla/mux v1.8.0 11 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 12 | github.com/pkg/errors v0.9.1 13 | github.com/rakyll/statik v0.1.7 14 | github.com/regen-network/cosmos-proto v0.3.1 15 | github.com/spf13/cast v1.3.1 16 | github.com/spf13/cobra v1.1.3 17 | github.com/spf13/viper v1.8.0 18 | github.com/stretchr/testify v1.7.0 19 | github.com/tendermint/tendermint v0.34.12 20 | github.com/tendermint/tm-db v0.6.4 21 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c 22 | google.golang.org/grpc v1.38.0 23 | google.golang.org/protobuf v1.26.0 24 | ) 25 | 26 | replace google.golang.org/grpc => google.golang.org/grpc v1.33.2 27 | 28 | replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 29 | 30 | replace github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 31 | -------------------------------------------------------------------------------- /module/third_party/proto/cosmos/base/query/v1beta1/pagination.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.base.query.v1beta1; 3 | 4 | option go_package = "github.com/cosmos/cosmos-sdk/types/query"; 5 | 6 | // PageRequest is to be embedded in gRPC request messages for efficient 7 | // pagination. Ex: 8 | // 9 | // message SomeRequest { 10 | // Foo some_parameter = 1; 11 | // PageRequest pagination = 2; 12 | // } 13 | message PageRequest { 14 | // key is a value returned in PageResponse.next_key to begin 15 | // querying the next page most efficiently. Only one of offset or key 16 | // should be set. 17 | bytes key = 1; 18 | 19 | // offset is a numeric offset that can be used when key is unavailable. 20 | // It is less efficient than using key. Only one of offset or key should 21 | // be set. 22 | uint64 offset = 2; 23 | 24 | // limit is the total number of results to be returned in the result page. 25 | // If left empty it will default to a value to be set by each app. 26 | uint64 limit = 3; 27 | 28 | // count_total is set to true to indicate that the result set should include 29 | // a count of the total number of items available for pagination in UIs. 30 | // count_total is only respected when offset is used. It is ignored when key 31 | // is set. 32 | bool count_total = 4; 33 | 34 | // reverse is set to true if results are to be returned in the descending order. 35 | bool reverse = 5; 36 | } 37 | 38 | // PageResponse is to be embedded in gRPC response messages where the 39 | // corresponding request message has used PageRequest. 40 | // 41 | // message SomeResponse { 42 | // repeated Bar results = 1; 43 | // PageResponse page = 2; 44 | // } 45 | message PageResponse { 46 | // next_key is the key to be passed to PageRequest.key to 47 | // query the next page most efficiently 48 | bytes next_key = 1; 49 | 50 | // total is total number of results available if PageRequest.count_total 51 | // was set, its value is undefined otherwise 52 | uint64 total = 2; 53 | } 54 | -------------------------------------------------------------------------------- /module/third_party/proto/cosmos/base/v1beta1/coin.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.base.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | 6 | option go_package = "github.com/cosmos/cosmos-sdk/types"; 7 | option (gogoproto.goproto_stringer_all) = false; 8 | option (gogoproto.stringer_all) = false; 9 | 10 | // Coin defines a token with a denomination and an amount. 11 | // 12 | // NOTE: The amount field is an Int which implements the custom method 13 | // signatures required by gogoproto. 14 | message Coin { 15 | option (gogoproto.equal) = true; 16 | 17 | string denom = 1; 18 | string amount = 2 [(gogoproto.customtype) = "Int", (gogoproto.nullable) = false]; 19 | } 20 | 21 | // DecCoin defines a token with a denomination and a decimal amount. 22 | // 23 | // NOTE: The amount field is an Dec which implements the custom method 24 | // signatures required by gogoproto. 25 | message DecCoin { 26 | option (gogoproto.equal) = true; 27 | 28 | string denom = 1; 29 | string amount = 2 [(gogoproto.customtype) = "Dec", (gogoproto.nullable) = false]; 30 | } 31 | 32 | // IntProto defines a Protobuf wrapper around an Int object. 33 | message IntProto { 34 | string int = 1 [(gogoproto.customtype) = "Int", (gogoproto.nullable) = false]; 35 | } 36 | 37 | // DecProto defines a Protobuf wrapper around a Dec object. 38 | message DecProto { 39 | string dec = 1 [(gogoproto.customtype) = "Dec", (gogoproto.nullable) = false]; 40 | } 41 | -------------------------------------------------------------------------------- /module/third_party/proto/cosmos_proto/cosmos.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos_proto; 3 | 4 | import "google/protobuf/descriptor.proto"; 5 | 6 | option go_package = "github.com/regen-network/cosmos-proto"; 7 | 8 | extend google.protobuf.MessageOptions { 9 | string interface_type = 93001; 10 | 11 | string implements_interface = 93002; 12 | } 13 | 14 | extend google.protobuf.FieldOptions { 15 | string accepts_interface = 93001; 16 | } 17 | -------------------------------------------------------------------------------- /module/third_party/proto/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /module/third_party/proto/google/api/httpbody.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/protobuf/any.proto"; 20 | 21 | option cc_enable_arenas = true; 22 | option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "HttpBodyProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | // Message that represents an arbitrary HTTP body. It should only be used for 29 | // payload formats that can't be represented as JSON, such as raw binary or 30 | // an HTML page. 31 | // 32 | // 33 | // This message can be used both in streaming and non-streaming API methods in 34 | // the request as well as the response. 35 | // 36 | // It can be used as a top-level request field, which is convenient if one 37 | // wants to extract parameters from either the URL or HTTP template into the 38 | // request fields and also want access to the raw HTTP body. 39 | // 40 | // Example: 41 | // 42 | // message GetResourceRequest { 43 | // // A unique request id. 44 | // string request_id = 1; 45 | // 46 | // // The raw HTTP body is bound to this field. 47 | // google.api.HttpBody http_body = 2; 48 | // } 49 | // 50 | // service ResourceService { 51 | // rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); 52 | // rpc UpdateResource(google.api.HttpBody) returns 53 | // (google.protobuf.Empty); 54 | // } 55 | // 56 | // Example with streaming methods: 57 | // 58 | // service CaldavService { 59 | // rpc GetCalendar(stream google.api.HttpBody) 60 | // returns (stream google.api.HttpBody); 61 | // rpc UpdateCalendar(stream google.api.HttpBody) 62 | // returns (stream google.api.HttpBody); 63 | // } 64 | // 65 | // Use of this type only changes how the request and response bodies are 66 | // handled, all other features will continue to work unchanged. 67 | message HttpBody { 68 | // The HTTP Content-Type header value specifying the content type of the body. 69 | string content_type = 1; 70 | 71 | // The HTTP request/response body as raw binary. 72 | bytes data = 2; 73 | 74 | // Application specific response metadata. Must be set in the first response 75 | // for streaming APIs. 76 | repeated google.protobuf.Any extensions = 3; 77 | } 78 | -------------------------------------------------------------------------------- /module/third_party/proto/tendermint/crypto/keys.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.crypto; 3 | 4 | option go_package = "github.com/tendermint/tendermint/proto/tendermint/crypto"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | 8 | // PublicKey defines the keys available for use with Tendermint Validators 9 | message PublicKey { 10 | option (gogoproto.compare) = true; 11 | option (gogoproto.equal) = true; 12 | 13 | oneof sum { 14 | bytes ed25519 = 1; 15 | } 16 | } 17 | 18 | // PrivateKey defines the keys available for use with Tendermint Validators 19 | // WARNING PrivateKey is used for internal purposes only 20 | message PrivateKey { 21 | oneof sum { 22 | bytes ed25519 = 1; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /module/third_party/proto/tendermint/crypto/proof.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.crypto; 3 | 4 | option go_package = "github.com/tendermint/tendermint/proto/tendermint/crypto"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | 8 | message Proof { 9 | int64 total = 1; 10 | int64 index = 2; 11 | bytes leaf_hash = 3; 12 | repeated bytes aunts = 4; 13 | } 14 | 15 | message ValueOp { 16 | // Encoded in ProofOp.Key. 17 | bytes key = 1; 18 | 19 | // To encode in ProofOp.Data 20 | Proof proof = 2; 21 | } 22 | 23 | message DominoOp { 24 | string key = 1; 25 | string input = 2; 26 | string output = 3; 27 | } 28 | 29 | // ProofOp defines an operation used for calculating Merkle root 30 | // The data could be arbitrary format, providing nessecary data 31 | // for example neighbouring node hash 32 | message ProofOp { 33 | string type = 1; 34 | bytes key = 2; 35 | bytes data = 3; 36 | } 37 | 38 | // ProofOps is Merkle proof defined by the list of ProofOps 39 | message ProofOps { 40 | repeated ProofOp ops = 1 [(gogoproto.nullable) = false]; 41 | } 42 | -------------------------------------------------------------------------------- /module/third_party/proto/tendermint/libs/bits/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.libs.bits; 3 | 4 | option go_package = "github.com/tendermint/tendermint/proto/tendermint/libs/bits"; 5 | 6 | message BitArray { 7 | int64 bits = 1; 8 | repeated uint64 elems = 2; 9 | } 10 | -------------------------------------------------------------------------------- /module/third_party/proto/tendermint/types/evidence.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.types; 3 | 4 | option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | import "google/protobuf/timestamp.proto"; 8 | import "tendermint/types/types.proto"; 9 | import "tendermint/crypto/keys.proto"; 10 | 11 | // DuplicateVoteEvidence contains evidence a validator signed two conflicting 12 | // votes. 13 | message DuplicateVoteEvidence { 14 | Vote vote_a = 1; 15 | Vote vote_b = 2; 16 | 17 | google.protobuf.Timestamp timestamp = 3 18 | [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; 19 | } 20 | 21 | message PotentialAmnesiaEvidence { 22 | Vote vote_a = 1; 23 | Vote vote_b = 2; 24 | 25 | int64 height_stamp = 3; 26 | google.protobuf.Timestamp timestamp = 4 27 | [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; 28 | } 29 | 30 | message AmnesiaEvidence { 31 | PotentialAmnesiaEvidence potential_amnesia_evidence = 1; 32 | ProofOfLockChange polc = 2; 33 | } 34 | 35 | message ConflictingHeadersEvidence { 36 | SignedHeader h1 = 1; 37 | SignedHeader h2 = 2; 38 | } 39 | 40 | message LunaticValidatorEvidence { 41 | Header header = 1; 42 | Vote vote = 2; 43 | string invalid_header_field = 3; 44 | 45 | google.protobuf.Timestamp timestamp = 4 46 | [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; 47 | } 48 | 49 | message Evidence { 50 | oneof sum { 51 | DuplicateVoteEvidence duplicate_vote_evidence = 1; 52 | ConflictingHeadersEvidence conflicting_headers_evidence = 2; 53 | LunaticValidatorEvidence lunatic_validator_evidence = 3; 54 | PotentialAmnesiaEvidence potential_amnesia_evidence = 4; 55 | AmnesiaEvidence amnesia_evidence = 5; 56 | } 57 | } 58 | 59 | // EvidenceData contains any evidence of malicious wrong-doing by validators 60 | message EvidenceData { 61 | repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; 62 | bytes hash = 2; 63 | } 64 | 65 | message ProofOfLockChange { 66 | repeated Vote votes = 1; 67 | tendermint.crypto.PublicKey pub_key = 2; 68 | } 69 | -------------------------------------------------------------------------------- /module/third_party/proto/tendermint/version/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.version; 3 | 4 | option go_package = "github.com/tendermint/tendermint/proto/tendermint/version"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | 8 | // App includes the protocol and software version for the application. 9 | // This information is included in ResponseInfo. The App.Protocol can be 10 | // updated in ResponseEndBlock. 11 | message App { 12 | uint64 protocol = 1; 13 | string software = 2; 14 | } 15 | 16 | // Consensus captures the consensus rules for processing a block in the blockchain, 17 | // including all blockchain data structures and the rules of the application's 18 | // state transition machine. 19 | message Consensus { 20 | option (gogoproto.equal) = true; 21 | 22 | uint64 block = 1; 23 | uint64 app = 2; 24 | } 25 | -------------------------------------------------------------------------------- /module/x/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/gravity-bridge/0195030967c3fef8f86da44ee89caa99564304cf/module/x/.gitkeep -------------------------------------------------------------------------------- /module/x/gravity/handler.go: -------------------------------------------------------------------------------- 1 | package gravity 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | 7 | "github.com/cosmos/gravity-bridge/module/x/gravity/keeper" 8 | "github.com/cosmos/gravity-bridge/module/x/gravity/types" 9 | ) 10 | 11 | // NewHandler returns a handler for "Gravity" type messages. 12 | func NewHandler(k keeper.Keeper) sdk.Handler { 13 | msgServer := keeper.NewMsgServerImpl(k) 14 | 15 | return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { 16 | ctx = ctx.WithEventManager(sdk.NewEventManager()) 17 | 18 | switch msg := msg.(type) { 19 | case *types.MsgSendToEthereum: 20 | res, err := msgServer.SendToEthereum(sdk.WrapSDKContext(ctx), msg) 21 | return sdk.WrapServiceResult(ctx, res, err) 22 | 23 | case *types.MsgCancelSendToEthereum: 24 | res, err := msgServer.CancelSendToEthereum(sdk.WrapSDKContext(ctx), msg) 25 | return sdk.WrapServiceResult(ctx, res, err) 26 | 27 | case *types.MsgRequestBatchTx: 28 | res, err := msgServer.RequestBatchTx(sdk.WrapSDKContext(ctx), msg) 29 | return sdk.WrapServiceResult(ctx, res, err) 30 | 31 | case *types.MsgSubmitEthereumTxConfirmation: 32 | res, err := msgServer.SubmitEthereumTxConfirmation(sdk.WrapSDKContext(ctx), msg) 33 | return sdk.WrapServiceResult(ctx, res, err) 34 | 35 | case *types.MsgSubmitEthereumEvent: 36 | res, err := msgServer.SubmitEthereumEvent(sdk.WrapSDKContext(ctx), msg) 37 | return sdk.WrapServiceResult(ctx, res, err) 38 | 39 | case *types.MsgDelegateKeys: 40 | res, err := msgServer.SetDelegateKeys(sdk.WrapSDKContext(ctx), msg) 41 | return sdk.WrapServiceResult(ctx, res, err) 42 | 43 | default: 44 | return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /module/x/gravity/keeper/ethereum_event_handler_test.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdktypes "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/stretchr/testify/require" 6 | "math/big" 7 | "testing" 8 | ) 9 | 10 | func TestEthereumEventProcessor_DetectMaliciousSupply(t *testing.T) { 11 | input := CreateTestEnv(t) 12 | eep := EthereumEventProcessor{keeper: input.GravityKeeper, bankKeeper: input.BankKeeper} 13 | 14 | // set supply to maximum value 15 | var testBigInt big.Int 16 | testBigInt.SetBit(new(big.Int), 256, 1).Sub(&testBigInt, big.NewInt(1)) 17 | bigCoinAmount := sdktypes.NewIntFromBigInt(&testBigInt) 18 | 19 | err := eep.DetectMaliciousSupply(input.Context, "stake", bigCoinAmount) 20 | require.Error(t, err, "didn't error out on too much added supply") 21 | } -------------------------------------------------------------------------------- /module/x/gravity/keeper/hooks.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 6 | "github.com/cosmos/gravity-bridge/module/x/gravity/types" 7 | ) 8 | 9 | type Hooks struct { 10 | k Keeper 11 | } 12 | 13 | var _ stakingtypes.StakingHooks = Hooks{} 14 | 15 | // Hooks Create new gravity hooks 16 | func (k Keeper) Hooks() Hooks { return Hooks{k} } 17 | 18 | func (h Hooks) AfterValidatorBeginUnbonding(ctx sdk.Context, _ sdk.ConsAddress, _ sdk.ValAddress) { 19 | 20 | // When Validator starts Unbonding, Persist the block height in the store 21 | // Later in endblocker, check if there is at least one validator who started unbonding and create a valset request. 22 | // The reason for creating valset requests in endblock is to create only one valset request per block, 23 | // if multiple validators starts unbonding at same block. 24 | 25 | h.k.setLastUnbondingBlockHeight(ctx, uint64(ctx.BlockHeight())) 26 | 27 | } 28 | 29 | func (h Hooks) BeforeDelegationCreated(_ sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { 30 | } 31 | func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {} 32 | func (h Hooks) BeforeValidatorModified(_ sdk.Context, _ sdk.ValAddress) {} 33 | func (h Hooks) AfterValidatorBonded(_ sdk.Context, _ sdk.ConsAddress, _ sdk.ValAddress) {} 34 | func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} 35 | func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) {} 36 | func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) {} 37 | func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { 38 | } 39 | func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { 40 | } 41 | 42 | var _ types.GravityHooks = Keeper{} 43 | 44 | func (k Keeper) AfterContractCallExecutedEvent(ctx sdk.Context, event types.ContractCallExecutedEvent) { 45 | if k.hooks != nil { 46 | k.hooks.AfterContractCallExecutedEvent(ctx, event) 47 | } 48 | } 49 | 50 | func (k Keeper) AfterERC20DeployedEvent(ctx sdk.Context, event types.ERC20DeployedEvent) { 51 | if k.hooks != nil { 52 | k.hooks.AfterERC20DeployedEvent(ctx, event) 53 | } 54 | } 55 | 56 | func (k Keeper) AfterSignerSetExecutedEvent(ctx sdk.Context, event types.SignerSetTxExecutedEvent) { 57 | if k.hooks != nil { 58 | k.hooks.AfterSignerSetExecutedEvent(ctx, event) 59 | } 60 | } 61 | 62 | func (k Keeper) AfterBatchExecutedEvent(ctx sdk.Context, event types.BatchExecutedEvent) { 63 | if k.hooks != nil { 64 | k.hooks.AfterBatchExecutedEvent(ctx, event) 65 | } 66 | } 67 | 68 | func (k Keeper) AfterSendToCosmosEvent(ctx sdk.Context, event types.SendToCosmosEvent) { 69 | if k.hooks != nil { 70 | k.hooks.AfterSendToCosmosEvent(ctx, event) 71 | } 72 | } 73 | 74 | func (k *Keeper) SetHooks(sh types.GravityHooks) *Keeper { 75 | if k.hooks != nil { 76 | panic("cannot set gravity hooks twice") 77 | } 78 | 79 | k.hooks = sh 80 | 81 | return k 82 | } 83 | -------------------------------------------------------------------------------- /module/x/gravity/keeper/pool_test.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "testing" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/cosmos/gravity-bridge/module/x/gravity/types" 11 | ) 12 | 13 | func TestAddToOutgoingPool(t *testing.T) { 14 | input := CreateTestEnv(t) 15 | ctx := input.Context 16 | var ( 17 | mySender, _ = sdk.AccAddressFromBech32("cosmos1ahx7f8wyertuus9r20284ej0asrs085case3kn") 18 | myReceiver = common.HexToAddress("0xd041c41EA1bf0F006ADBb6d2c9ef9D425dE5eaD7") 19 | myTokenContractAddr = common.HexToAddress("0x429881672B9AE42b8EbA0E26cD9C73711b891Ca5") 20 | ) 21 | // mint some voucher first 22 | allVouchers := sdk.Coins{types.NewERC20Token(99999, myTokenContractAddr.Hex()).GravityCoin()} 23 | err := input.BankKeeper.MintCoins(ctx, types.ModuleName, allVouchers) 24 | require.NoError(t, err) 25 | 26 | // set senders balance 27 | input.AccountKeeper.NewAccountWithAddress(ctx, mySender) 28 | require.NoError(t, fundAccount(ctx, input.BankKeeper, mySender, allVouchers)) 29 | 30 | // when 31 | input.AddSendToEthTxsToPool(t, ctx, myTokenContractAddr, mySender, myReceiver, 2, 3, 2, 1) 32 | 33 | // then 34 | var got []*types.SendToEthereum 35 | input.GravityKeeper.IterateUnbatchedSendToEthereums(ctx, func(tx *types.SendToEthereum) bool { 36 | got = append(got, tx) 37 | return false 38 | }) 39 | 40 | exp := []*types.SendToEthereum{ 41 | types.NewSendToEthereumTx(2, myTokenContractAddr, mySender, myReceiver, 101, 3), 42 | types.NewSendToEthereumTx(3, myTokenContractAddr, mySender, myReceiver, 102, 2), 43 | types.NewSendToEthereumTx(1, myTokenContractAddr, mySender, myReceiver, 100, 2), 44 | types.NewSendToEthereumTx(4, myTokenContractAddr, mySender, myReceiver, 103, 1), 45 | } 46 | 47 | require.Equal(t, exp, got) 48 | require.EqualValues(t, exp[0], got[0]) 49 | require.EqualValues(t, exp[1], got[1]) 50 | require.EqualValues(t, exp[2], got[2]) 51 | require.EqualValues(t, exp[3], got[3]) 52 | require.Len(t, got, 4) 53 | } 54 | -------------------------------------------------------------------------------- /module/x/gravity/spec/01_definitions.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | # Definitions 7 | 8 | This section outlines terminology used throughout the spec and code. 9 | 10 | ### Operator 11 | 12 | This is a person (or people) who control a Cosmos SDK validator node. This is also called `valoper` or "Validator Operator" in the Cosmos SDK staking section 13 | 14 | ### Counter Chain 15 | 16 | A chain that utilizes an EVM. Some examples of this are Polygon, Ethereum, and Ethereum Classic. 17 | 18 | ### Relayer 19 | 20 | This is a type of node that submits updates to the Gravity contract on the counter chain and vice versa. It earns fees from the transactions in a batch. 21 | 22 | ### Gravity Tx Pool 23 | 24 | Is a transaction pool that exists in the chain store of Cosmos -> Ethereum transactions waiting to be placed into a transaction batch 25 | 26 | ### Transaction Batch 27 | 28 | A transaction batch is a set of Ethereum transactions to be sent from the Gravity Ethereum contract at the same time. This helps reduce the costs of submitting a batch. Batches have a maximum size (currently around 100 transactions) and are only involved in the Cosmos -> Ethereum flow 29 | 30 | ### Gravity Batch Pool 31 | 32 | Is a transaction pool like structure that exists in the chains to store, separate from the `Gravity Tx Pool` it stores transactions that have been placed in batches that are in the process of being signed or being submitted by the `Orchestrator Set` 33 | 34 | ### Observed 35 | 36 | Events on Ethereum are considered `Observed` when the `Eth Signers` of 66% of the active Cosmos validator set during a given block has submitted an oracle message attesting to seeing the event. 37 | 38 | ### Validator Set Delta 39 | 40 | This is a term for the difference between the validator set currently in the Gravity Ethereum contract and the actual validator set on the Cosmos chain. Since the validator set may change every single block there is essentially guaranteed to be some nonzero `Validator set delta` at any given time. 41 | 42 | ### Claim 43 | 44 | An Ethereum event signed and submitted to cosmos by a single `Orchestrator` instance. 45 | 46 | ### Attestation 47 | 48 | Aggregate of claims that eventually becomes `observed` by all orchestrators. 49 | 50 | ### Voucher 51 | 52 | Represents a bridged ETH token on the Cosmos side. Their denom is has a `gravity` prefix and a hash that is build from contract address and contract token. The denom is considered unique within the system. 53 | 54 | ### Counterpart 55 | 56 | A `Voucher` which is the locked opposing chain token in the contract 57 | 58 | ### Logic Calls 59 | 60 | A logic call refers to a created action for a smart contract interaction on the opposing chain. 61 | -------------------------------------------------------------------------------- /module/x/gravity/spec/03_state_transitions.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # State Transitions 6 | 7 | This document describes the state transition operations pertaining to: 8 | -------------------------------------------------------------------------------- /module/x/gravity/spec/05_end_block.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # End-Block 6 | 7 | Each abci end block call, the operations to update queues and validator set 8 | changes are specified to execute. 9 | 10 | ## Slashing 11 | 12 | Slashing groups multiple types of slashing (validator set, batch and claim slashing). We will cover how these work in the following sections. 13 | 14 | ### Validator Slashing 15 | 16 | A validator is slashed for not signing over a validatorset. The Cosmos-SDK allows active validator sets to change from block to block, for this reason we need to store multiple validator sets within a single unbonding period. This allows validators to not be slashed. 17 | 18 | A validator will be slashed or missing a single confirmation signing. 19 | 20 | ### Batch Slashing 21 | 22 | A validator is slashed for not signing over a batch request. A validator will be slashed for missing 23 | 24 | ## Attestation 25 | 26 | Iterates through all attestations currently being voted on. Once an attestation nonce one higher than the previous one, we stop searching for an attestation and call `TryAttestation`. Once an attestation at a specific nonce has enough votes all the other attestations will be skipped and the `lastObservedEventNonce` incremented. 27 | 28 | ## Cleanup 29 | 30 | Cleanup loops through batches and logic calls in order to clean up the timed out transactions. 31 | 32 | ### Batches 33 | 34 | When a batch of transactions are created they have a specified height of the opposing chain for when the batch becomes invalid. When this happens we must remove them from the store. At the end of every block, we loop through the store of logic calls checking the the timeout heights. 35 | 36 | ### Logic Calls 37 | 38 | When a logic call is created it consists of a timeout height. This height is used to know when the logic call becomes invalid. At the end of every block, we loop through the store of logic calls checking the the timeout heights. 39 | -------------------------------------------------------------------------------- /module/x/gravity/spec/07_params.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Parameters 6 | 7 | The gravity module contains the following parameters: 8 | 9 | | Key | Type | Example | 10 | |-------------------------------|--------------|----------------| 11 | | gravityId | string | "gravity" | 12 | | ContractSourceHash | string | "special hash" | 13 | | BridgeEthereumAddress | string | "0x1" | 14 | | BridgeChainId | uint64 | 4 | 15 | | SignedValsetsWindow | uint64 | 10_000 | 16 | | SignedBatchesWindow | uint64 | 10_000 | 17 | | SignedClaimsWindow | uint64 | 10_000 | 18 | | TargetEthTxTimeout | uint64 | 43_200_000 | 19 | | AverageBlockTime | uint64 | 5_000 | 20 | | AverageEthereumBlockTime | uint64 | 15_000 | 21 | | SlashFractionValset | sdkTypes.Dec | - | 22 | | SlashFractionBatch | sdkTypes.Dec | - | 23 | | SlashFractionClaim | sdkTypes.Dec | - | 24 | | SlashFractionConflictingClaim | sdkTypes.Dec | - | 25 | | UnbondSlashingValsetsWindow | uint64 | 3 | 26 | | UnbondSlashingBatchWindow | uint64 | 3 | 27 | -------------------------------------------------------------------------------- /module/x/gravity/spec/README.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # `gravity` 9 | 10 | ## Abstract 11 | 12 | ## Contents 13 | -------------------------------------------------------------------------------- /module/x/gravity/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 5 | ) 6 | 7 | var ( 8 | ErrInvalid = sdkerrors.Register(ModuleName, 3, "invalid") 9 | ErrSupplyOverflow = sdkerrors.Register(ModuleName, 4, "malicious ERC20 with invalid supply sent over bridge") 10 | ErrDelegateKeys = sdkerrors.Register(ModuleName, 5, "failed to delegate keys") 11 | ErrEmptyEthSig = sdkerrors.Register(ModuleName, 6, "empty Ethereum signature") 12 | ErrInvalidERC20Event = sdkerrors.Register(ModuleName, 7, "invalid ERC20 deployed event") 13 | ) 14 | -------------------------------------------------------------------------------- /module/x/gravity/types/ethereum_signature.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 7 | "github.com/ethereum/go-ethereum/common" 8 | ) 9 | 10 | var ( 11 | _ EthereumTxConfirmation = &SignerSetTxConfirmation{} 12 | _ EthereumTxConfirmation = &ContractCallTxConfirmation{} 13 | _ EthereumTxConfirmation = &BatchTxConfirmation{} 14 | ) 15 | 16 | /////////////// 17 | // GetSigner // 18 | /////////////// 19 | 20 | func (u *SignerSetTxConfirmation) GetSigner() common.Address { 21 | return common.HexToAddress(u.EthereumSigner) 22 | } 23 | 24 | func (u *ContractCallTxConfirmation) GetSigner() common.Address { 25 | return common.HexToAddress(u.EthereumSigner) 26 | } 27 | 28 | func (u *BatchTxConfirmation) GetSigner() common.Address { 29 | return common.HexToAddress(u.EthereumSigner) 30 | } 31 | 32 | /////////////////// 33 | // GetStoreIndex // 34 | /////////////////// 35 | 36 | func (sstx *SignerSetTxConfirmation) GetStoreIndex() []byte { 37 | return MakeSignerSetTxKey(sstx.SignerSetNonce) 38 | } 39 | 40 | func (btx *BatchTxConfirmation) GetStoreIndex() []byte { 41 | return MakeBatchTxKey(common.HexToAddress(btx.TokenContract), btx.BatchNonce) 42 | } 43 | 44 | func (cctx *ContractCallTxConfirmation) GetStoreIndex() []byte { 45 | return MakeContractCallTxKey(cctx.InvalidationScope, cctx.InvalidationNonce) 46 | } 47 | 48 | ////////////// 49 | // Validate // 50 | ////////////// 51 | 52 | func (u *SignerSetTxConfirmation) Validate() error { 53 | if u.SignerSetNonce == 0 { 54 | return fmt.Errorf("nonce must be set") 55 | } 56 | if !common.IsHexAddress(u.EthereumSigner) { 57 | return sdkerrors.Wrap(ErrInvalid, "ethereum signer must be address") 58 | } 59 | if u.Signature == nil { 60 | return fmt.Errorf("signature must be set") 61 | } 62 | return nil 63 | } 64 | 65 | func (u *ContractCallTxConfirmation) Validate() error { 66 | if u.InvalidationNonce == 0 { 67 | return fmt.Errorf("invalidation nonce must be set") 68 | } 69 | if u.InvalidationScope == nil { 70 | return fmt.Errorf("invalidation scope must be set") 71 | } 72 | if !common.IsHexAddress(u.EthereumSigner) { 73 | return sdkerrors.Wrap(ErrInvalid, "ethereum signer must be address") 74 | } 75 | if u.Signature == nil { 76 | return fmt.Errorf("signature must be set") 77 | } 78 | return nil 79 | } 80 | 81 | func (u *BatchTxConfirmation) Validate() error { 82 | if u.BatchNonce == 0 { 83 | return fmt.Errorf("nonce must be set") 84 | } 85 | if !common.IsHexAddress(u.TokenContract) { 86 | return fmt.Errorf("token contract address must be valid ethereum address") 87 | } 88 | if !common.IsHexAddress(u.EthereumSigner) { 89 | return sdkerrors.Wrap(ErrInvalid, "ethereum signer must be address") 90 | } 91 | if u.Signature == nil { 92 | return fmt.Errorf("signature must be set") 93 | } 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /module/x/gravity/types/ethereum_signer.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | 6 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/crypto" 9 | ) 10 | 11 | const ( 12 | signaturePrefix = "\x19Ethereum Signed Message:\n32" 13 | ) 14 | 15 | // NewEthereumSignature creates a new signuature over a given byte array 16 | func NewEthereumSignature(hash []byte, privateKey *ecdsa.PrivateKey) ([]byte, error) { 17 | if privateKey == nil { 18 | return nil, sdkerrors.Wrap(ErrInvalid, "did not pass in private key") 19 | } 20 | protectedHash := crypto.Keccak256Hash(append([]byte(signaturePrefix), hash...)) 21 | return crypto.Sign(protectedHash.Bytes(), privateKey) 22 | } 23 | 24 | // ValidateEthereumSignature takes a message, an associated signature and public key and 25 | // returns an error if the signature isn't valid 26 | func ValidateEthereumSignature(hash []byte, signature []byte, ethAddress common.Address) error { 27 | 28 | /// signature to public key: invalid signature length: invalid 29 | /// signature not matching: invalid: invalid 30 | if len(signature) < 65 { 31 | return sdkerrors.Wrapf(ErrInvalid, "signature too short signature %x", signature) 32 | } 33 | 34 | // Copy to avoid mutating signature slice by accident 35 | var sigCopy = make([]byte, len(signature)) 36 | copy(sigCopy, signature) 37 | 38 | // To verify signature 39 | // - use crypto.SigToPub to get the public key 40 | // - use crypto.PubkeyToAddress to get the address 41 | // - compare this to the address given. 42 | 43 | // for backwards compatibility reasons the V value of an Ethereum sig is presented 44 | // as 27 or 28, internally though it should be a 0-3 value due to changed formats. 45 | // It seems that go-ethereum expects this to be done before sigs actually reach it's 46 | // internal validation functions. In order to comply with this requirement we check 47 | // the sig an dif it's in standard format we correct it. If it's in go-ethereum's expected 48 | // format already we make no changes. 49 | // 50 | // We could attempt to break or otherwise exit early on obviously invalid values for this 51 | // byte, but that's a task best left to go-ethereum 52 | if sigCopy[64] == 27 || sigCopy[64] == 28 { 53 | sigCopy[64] -= 27 54 | } 55 | 56 | hash = append([]uint8(signaturePrefix), hash...) 57 | 58 | pubkey, err := crypto.SigToPub(crypto.Keccak256Hash(hash).Bytes(), sigCopy) 59 | if err != nil { 60 | return sdkerrors.Wrapf(err, "signature to public key sig %x hash %x", sigCopy, hash) 61 | } 62 | 63 | if addr := crypto.PubkeyToAddress(*pubkey); addr != ethAddress { 64 | return sdkerrors.Wrapf(ErrInvalid, "signature not matching addr %x sig %x hash %x", addr, signature, hash) 65 | } 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /module/x/gravity/types/ethereum_signer_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSignerSetConfirmSig(t *testing.T) { 13 | const ( 14 | correctSig = "e108a7776de6b87183b0690484a74daef44aa6daf907e91abaf7bbfa426ae7706b12e0bd44ef7b0634710d99c2d81087a2f39e075158212343a3b2948ecf33d01c" 15 | invalidSig = "fffff7776de6b87183b0690484a74daef44aa6daf907e91abaf7bbfa426ae7706b12e0bd44ef7b0634710d99c2d81087a2f39e075158212343a3b2948ecf33d01c" 16 | ethAddress = "0xc783df8a850f42e7F7e57013759C285caa701eB6" 17 | hash = "88165860d955aee7dc3e83d9d1156a5864b708841965585d206dbef6e9e1a499" 18 | ) 19 | 20 | specs := map[string]struct { 21 | srcHash string 22 | srcSignature string 23 | srcETHAddr string 24 | expErr bool 25 | }{ 26 | "all good": { 27 | srcHash: hash, 28 | srcSignature: correctSig, 29 | srcETHAddr: ethAddress, 30 | }, 31 | "invalid signature": { 32 | srcHash: hash, 33 | srcSignature: invalidSig, 34 | srcETHAddr: ethAddress, 35 | expErr: true, 36 | }, 37 | "empty hash": { 38 | srcSignature: correctSig, 39 | srcETHAddr: ethAddress, 40 | expErr: true, 41 | }, 42 | "hash too short": { 43 | srcSignature: correctSig, 44 | srcETHAddr: ethAddress, 45 | srcHash: hash[0:30], 46 | expErr: true, 47 | }, 48 | "hash too long": { 49 | srcSignature: correctSig, 50 | srcETHAddr: ethAddress, 51 | srcHash: hash + "01", 52 | expErr: true, 53 | }, 54 | "empty signature": { 55 | srcHash: hash, 56 | srcETHAddr: ethAddress, 57 | expErr: true, 58 | }, 59 | "signature too short": { 60 | srcHash: hash, 61 | srcSignature: correctSig[0:64], 62 | srcETHAddr: ethAddress, 63 | expErr: true, 64 | }, 65 | "empty eth address": { 66 | srcHash: hash, 67 | srcSignature: correctSig, 68 | expErr: true, 69 | }, 70 | } 71 | for msg, spec := range specs { 72 | t.Run(msg, func(t *testing.T) { 73 | var err error 74 | var hashBytes []byte 75 | if len(spec.srcHash) != 0 { 76 | hashBytes, err = hex.DecodeString(spec.srcHash) 77 | require.NoError(t, err) 78 | } 79 | sigBytes, err := hex.DecodeString(spec.srcSignature) 80 | require.NoError(t, err) 81 | 82 | // when 83 | err = ValidateEthereumSignature(hashBytes, sigBytes, common.HexToAddress(spec.srcETHAddr)) 84 | if spec.expErr { 85 | assert.Error(t, err) 86 | return 87 | } 88 | assert.NoError(t, err) 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /module/x/gravity/types/events.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | EventTypeObservation = "observation" 5 | EventTypeOutgoingBatch = "outgoing_batch" 6 | EventTypeMultisigUpdateRequest = "multisig_update_request" 7 | EventTypeOutgoingBatchCanceled = "outgoing_batch_canceled" 8 | EventTypeContractCallTxCanceled = "outgoing_logic_call_canceled" 9 | EventTypeBridgeWithdrawalReceived = "withdrawal_received" 10 | EventTypeBridgeDepositReceived = "deposit_received" 11 | EventTypeBridgeWithdrawCanceled = "withdraw_canceled" 12 | 13 | AttributeKeyEthereumEventVoteRecordID = "ethereum_event_vote_record_id" 14 | AttributeKeyBatchConfirmKey = "batch_confirm_key" 15 | AttributeKeyEthereumSignatureKey = "ethereum_signature_key" 16 | AttributeKeyOutgoingBatchID = "batch_id" 17 | AttributeKeyOutgoingTXID = "outgoing_tx_id" 18 | AttributeKeyEthereumEventType = "ethereum_event_type" 19 | AttributeKeyContract = "bridge_contract" 20 | AttributeKeyNonce = "nonce" 21 | AttributeKeySignerSetNonce = "signerset_nonce" 22 | AttributeKeyBatchNonce = "batch_nonce" 23 | AttributeKeyBridgeChainID = "bridge_chain_id" 24 | AttributeKeySetOrchestratorAddr = "set_orchestrator_address" 25 | AttributeKeySetEthereumAddr = "set_ethereum_address" 26 | AttributeKeyValidatorAddr = "validator_address" 27 | AttributeKeyContractCallInvalidationScope = "contract_call_invalidation_scope" 28 | AttributeKeyContractCallInvalidationNonce = "contract_call_invalidation_nonce" 29 | AttributeKeyContractCallPayload = "contract_call_payload" 30 | AttributeKeyContractCallTokens = "contract_call_tokens" 31 | AttributeKeyContractCallFees = "contract_call_fees" 32 | AttributeKeyEthTxTimeout = "eth_tx_timeout" 33 | ) 34 | -------------------------------------------------------------------------------- /module/x/gravity/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "time" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | bank "github.com/cosmos/cosmos-sdk/x/bank/types" 8 | slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" 9 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 10 | ) 11 | 12 | // StakingKeeper defines the expected staking keeper methods 13 | type StakingKeeper interface { 14 | GetBondedValidatorsByPower(ctx sdk.Context) []stakingtypes.Validator 15 | GetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress) int64 16 | GetLastTotalPower(ctx sdk.Context) (power sdk.Int) 17 | IterateValidators(sdk.Context, func(index int64, validator stakingtypes.ValidatorI) (stop bool)) 18 | ValidatorQueueIterator(ctx sdk.Context, endTime time.Time, endHeight int64) sdk.Iterator 19 | GetParams(ctx sdk.Context) stakingtypes.Params 20 | GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool) 21 | IterateBondedValidatorsByPower(sdk.Context, func(index int64, validator stakingtypes.ValidatorI) (stop bool)) 22 | IterateLastValidators(sdk.Context, func(index int64, validator stakingtypes.ValidatorI) (stop bool)) 23 | Validator(sdk.Context, sdk.ValAddress) stakingtypes.ValidatorI 24 | ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) stakingtypes.ValidatorI 25 | Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) 26 | Jail(sdk.Context, sdk.ConsAddress) 27 | } 28 | 29 | // BankKeeper defines the expected bank keeper methods 30 | type BankKeeper interface { 31 | GetSupply(ctx sdk.Context, denom string) sdk.Coin 32 | SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error 33 | SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error 34 | SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 35 | MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error 36 | BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error 37 | GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 38 | GetDenomMetaData(ctx sdk.Context, denom string) (bank.Metadata, bool) 39 | } 40 | 41 | type SlashingKeeper interface { 42 | GetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (info slashingtypes.ValidatorSigningInfo, found bool) 43 | } 44 | 45 | // AccountKeeper defines the interface contract required for account 46 | // functionality. 47 | type AccountKeeper interface { 48 | GetSequence(ctx sdk.Context, addr sdk.AccAddress) (uint64, error) 49 | } 50 | -------------------------------------------------------------------------------- /module/x/gravity/types/genesis_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestGenesisStateValidate(t *testing.T) { 10 | specs := map[string]struct { 11 | src *GenesisState 12 | expErr bool 13 | }{ 14 | "default params": {src: DefaultGenesisState(), expErr: false}, 15 | "empty params": {src: &GenesisState{Params: &Params{}}, expErr: true}, 16 | "invalid params": {src: &GenesisState{ 17 | Params: &Params{ 18 | GravityId: "foo", 19 | ContractSourceHash: "laksdjflasdkfja", 20 | BridgeEthereumAddress: "invalid-eth-address", 21 | BridgeChainId: 3279089, 22 | }, 23 | }, expErr: true}, 24 | } 25 | for msg, spec := range specs { 26 | t.Run(msg, func(t *testing.T) { 27 | err := spec.src.ValidateBasic() 28 | if spec.expErr { 29 | require.Error(t, err) 30 | return 31 | } 32 | require.NoError(t, err) 33 | }) 34 | } 35 | } 36 | 37 | func TestStringToByteArray(t *testing.T) { 38 | specs := map[string]struct { 39 | testString string 40 | expErr bool 41 | }{ 42 | "16 bytes": {"lakjsdflaksdjfds", false}, 43 | "32 bytes": {"lakjsdflaksdjfdslakjsdflaksdjfds", false}, 44 | "33 bytes": {"€€€€€€€€€€€", true}, 45 | } 46 | 47 | for msg, spec := range specs { 48 | t.Run(msg, func(t *testing.T) { 49 | _, err := strToFixByteArray(spec.testString) 50 | if spec.expErr { 51 | require.Error(t, err) 52 | return 53 | } 54 | require.NoError(t, err) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /module/x/gravity/types/hooks.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import sdk "github.com/cosmos/cosmos-sdk/types" 4 | 5 | type GravityHooks interface { 6 | AfterContractCallExecutedEvent(ctx sdk.Context, event ContractCallExecutedEvent) 7 | AfterERC20DeployedEvent(ctx sdk.Context, event ERC20DeployedEvent) 8 | AfterSignerSetExecutedEvent(ctx sdk.Context, event SignerSetTxExecutedEvent) 9 | AfterBatchExecutedEvent(ctx sdk.Context, event BatchExecutedEvent) 10 | AfterSendToCosmosEvent(ctx sdk.Context, event SendToCosmosEvent) 11 | } 12 | 13 | type MultiGravityHooks []GravityHooks 14 | 15 | func NewMultiGravityHooks(hooks ...GravityHooks) MultiGravityHooks { 16 | return hooks 17 | } 18 | 19 | func (mghs MultiGravityHooks) AfterContractCallExecutedEvent(ctx sdk.Context, event ContractCallExecutedEvent) { 20 | for i := range mghs { 21 | mghs[i].AfterContractCallExecutedEvent(ctx, event) 22 | } 23 | } 24 | 25 | func (mghs MultiGravityHooks) AfterERC20DeployedEvent(ctx sdk.Context, event ERC20DeployedEvent) { 26 | for i := range mghs { 27 | mghs[i].AfterERC20DeployedEvent(ctx, event) 28 | } 29 | } 30 | 31 | func (mghs MultiGravityHooks) AfterSignerSetExecutedEvent(ctx sdk.Context, event SignerSetTxExecutedEvent) { 32 | for i := range mghs { 33 | mghs[i].AfterSignerSetExecutedEvent(ctx, event) 34 | } 35 | } 36 | 37 | func (mghs MultiGravityHooks) AfterBatchExecutedEvent(ctx sdk.Context, event BatchExecutedEvent) { 38 | for i := range mghs { 39 | mghs[i].AfterBatchExecutedEvent(ctx, event) 40 | } 41 | } 42 | 43 | func (mghs MultiGravityHooks) AfterSendToCosmosEvent(ctx sdk.Context, event SendToCosmosEvent) { 44 | for i := range mghs { 45 | mghs[i].AfterSendToCosmosEvent(ctx, event) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /module/x/gravity/types/interfaces.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/gogo/protobuf/proto" 6 | tmbytes "github.com/tendermint/tendermint/libs/bytes" 7 | ) 8 | 9 | // EthereumTxConfirmation represents one validtors signature for a given 10 | // outgoing ethereum transaction 11 | type EthereumTxConfirmation interface { 12 | proto.Message 13 | 14 | GetSigner() common.Address 15 | GetSignature() []byte 16 | GetStoreIndex() []byte 17 | Validate() error 18 | } 19 | 20 | // EthereumEvent represents a event from the gravity contract 21 | // on the counterparty ethereum chain 22 | type EthereumEvent interface { 23 | proto.Message 24 | 25 | GetEventNonce() uint64 26 | GetEthereumHeight() uint64 27 | Hash() tmbytes.HexBytes 28 | Validate() error 29 | } 30 | 31 | type OutgoingTx interface { 32 | // NOTE: currently the function signatures here don't match, figure out how to do this proprly 33 | // maybe add an interface arg here and typecheck in each implementation? 34 | 35 | // The only one that will be problematic is BatchTx which needs to pull all the constituent 36 | // transactions before calculating the checkpoint 37 | GetCheckpoint([]byte) []byte 38 | GetStoreIndex() []byte 39 | GetCosmosHeight() uint64 40 | } 41 | -------------------------------------------------------------------------------- /module/x/gravity/types/msgs_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/stretchr/testify/assert" 9 | 10 | "github.com/cosmos/gravity-bridge/module/app" 11 | "github.com/cosmos/gravity-bridge/module/x/gravity/types" 12 | ) 13 | 14 | func TestValidateMsgDelegateKeys(t *testing.T) { 15 | app.SetAddressConfig() 16 | 17 | var ( 18 | ethAddress = "0xb462864E395d88d6bc7C5dd5F3F5eb4cc2599255" 19 | cosmosAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, app.MaxAddrLen) 20 | valAddress sdk.ValAddress = bytes.Repeat([]byte{0x1}, app.MaxAddrLen) 21 | ) 22 | specs := map[string]struct { 23 | srcCosmosAddr sdk.AccAddress 24 | srcValAddr sdk.ValAddress 25 | srcETHAddr string 26 | expErr bool 27 | }{ 28 | "all good": { 29 | srcCosmosAddr: cosmosAddress, 30 | srcValAddr: valAddress, 31 | srcETHAddr: ethAddress, 32 | }, 33 | "empty validator address": { 34 | srcETHAddr: ethAddress, 35 | srcCosmosAddr: cosmosAddress, 36 | expErr: true, 37 | }, 38 | "invalid validator address": { 39 | srcValAddr: []byte{0x1}, 40 | srcCosmosAddr: cosmosAddress, 41 | srcETHAddr: ethAddress, 42 | expErr: true, 43 | }, 44 | "empty cosmos address": { 45 | srcValAddr: valAddress, 46 | srcETHAddr: ethAddress, 47 | expErr: true, 48 | }, 49 | "invalid cosmos address": { 50 | srcCosmosAddr: []byte{0x1}, 51 | srcValAddr: valAddress, 52 | srcETHAddr: ethAddress, 53 | expErr: true, 54 | }, 55 | "empty eth address": { 56 | srcValAddr: valAddress, 57 | srcCosmosAddr: cosmosAddress, 58 | expErr: true, 59 | }, 60 | "invalid eth address": { 61 | srcValAddr: valAddress, 62 | srcCosmosAddr: cosmosAddress, 63 | srcETHAddr: "invalid", 64 | expErr: true, 65 | }, 66 | } 67 | for msg, spec := range specs { 68 | t.Run(msg, func(t *testing.T) { 69 | msg := types.NewMsgDelegateKeys(spec.srcValAddr, spec.srcCosmosAddr, spec.srcETHAddr, []byte{0x1}) 70 | err := msg.ValidateBasic() 71 | if spec.expErr { 72 | assert.Error(t, err) 73 | return 74 | } 75 | 76 | assert.NoError(t, err) 77 | }) 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /orchestrator/.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .git 3 | .gitignore 4 | Dockerfile 5 | testnet.Dockerfile 6 | target/ -------------------------------------------------------------------------------- /orchestrator/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /orchestrator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | default-members = ["gorc", "orchestrator", "test_runner"] 3 | members = [ 4 | "orchestrator", 5 | "cosmos_gravity", 6 | "ethereum_gravity", 7 | "gravity_utils", 8 | "proto_build", 9 | "test_runner", 10 | "gravity_proto", 11 | "relayer", 12 | "client", 13 | "register_delegate_keys", 14 | "gorc", 15 | ] 16 | -------------------------------------------------------------------------------- /orchestrator/Dockerfile: -------------------------------------------------------------------------------- 1 | # Reference: https://www.lpalmieri.com/posts/fast-rust-docker-builds/ 2 | 3 | FROM rust:1.52 as cargo-chef-rust 4 | RUN apt-get install bash 5 | RUN cargo install cargo-chef 6 | 7 | FROM cargo-chef-rust as planner 8 | WORKDIR app 9 | # We only pay the installation cost once, 10 | # it will be cached from the second build onwards 11 | # To ensure a reproducible build consider pinning 12 | # the cargo-chef version with `--version X.X.X` 13 | COPY . . 14 | RUN cargo chef prepare --recipe-path recipe.json 15 | 16 | FROM cargo-chef-rust as cacher 17 | WORKDIR app 18 | COPY --from=planner /app/recipe.json recipe.json 19 | RUN cargo chef cook --release --recipe-path recipe.json 20 | 21 | FROM cargo-chef-rust as builder 22 | WORKDIR app 23 | COPY . . 24 | # Copy over the cached dependencies 25 | COPY --from=cacher /app/target target 26 | COPY --from=cacher /usr/local/cargo /usr/local/cargo 27 | RUN cargo build --release --bin orchestrator 28 | 29 | FROM cargo-chef-rust as runtime 30 | WORKDIR app 31 | COPY startup.sh startup.sh 32 | COPY --from=builder /app/target/release/orchestrator /usr/local/bin 33 | CMD sh startup.sh -------------------------------------------------------------------------------- /orchestrator/ci.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:edge 2 | 3 | COPY target/release/orchestrator /usr/bin/orchestrator 4 | COPY startup.sh startup.sh 5 | 6 | CMD sh startup.sh -------------------------------------------------------------------------------- /orchestrator/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.4.1" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ethereum_gravity = {path = "../ethereum_gravity"} 9 | cosmos_gravity = {path = "../cosmos_gravity"} 10 | gravity_utils = {path = "../gravity_utils"} 11 | gravity_proto = {path = "../gravity_proto/"} 12 | 13 | deep_space ={git="https://github.com/iqlusioninc/deep_space/", branch="zaki/tendermint_0_21"} 14 | serde_derive = "1.0" 15 | clarity = "0.4.11" 16 | docopt = "1" 17 | serde = "1.0" 18 | actix-rt = "2.2" 19 | lazy_static = "1" 20 | url = "2" 21 | web30 = "0.14.4" 22 | env_logger = "0.8" 23 | openssl-probe = "0.1" 24 | tokio = "1.4" -------------------------------------------------------------------------------- /orchestrator/cosmos_gravity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cosmos_gravity" 3 | version = "0.1.0" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | gravity_utils = {path = "../gravity_utils"} 11 | ethereum_gravity = {path = "../ethereum_gravity"} 12 | gravity_proto = {path = "../gravity_proto/"} 13 | 14 | deep_space ={git="https://github.com/iqlusioninc/deep_space/", branch="zaki/tendermint_0_21"} 15 | clarity = "0.4.11" 16 | serde = "1.0" 17 | num256 = "0.3" 18 | log = "0.4" 19 | sha3 = "0.9" 20 | tokio = "1.4" 21 | web30 = "0.14.4" 22 | tonic = "0.4" 23 | cosmos-sdk-proto = {git="http://github.com/cosmos/cosmos-rust", branch="main"} 24 | prost = "0.7" 25 | prost-types = "0.7" 26 | bytes = "1" 27 | 28 | [dev-dependencies] 29 | env_logger = "0.8" 30 | rand = "0.8" 31 | actix = "0.11" -------------------------------------------------------------------------------- /orchestrator/cosmos_gravity/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains various components and utilities for interacting with the Gravity Cosmos module. Primarily 2 | //! Extensions to Althea's 'deep_space' Cosmos transaction library to allow it to send Gravity module specific messages 3 | //! parse Gravity module specific endpoints and generally interact with the multitude of Gravity specific functionality 4 | //! that's part of the Cosmos module. 5 | 6 | #[macro_use] 7 | extern crate log; 8 | 9 | pub mod build; 10 | pub mod query; 11 | pub mod send; 12 | pub mod utils; -------------------------------------------------------------------------------- /orchestrator/cosmos_gravity/src/utils.rs: -------------------------------------------------------------------------------- 1 | use deep_space::Contact; 2 | use std::time::Duration; 3 | 4 | pub async fn wait_for_cosmos_online(contact: &Contact, timeout: Duration) { 5 | // we have a block now, wait for a few more. 6 | contact.wait_for_next_block(timeout).await.unwrap(); 7 | contact.wait_for_next_block(timeout).await.unwrap(); 8 | contact.wait_for_next_block(timeout).await.unwrap(); 9 | } 10 | -------------------------------------------------------------------------------- /orchestrator/ethereum_gravity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ethereum_gravity" 3 | version = "0.1.0" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | gravity_utils = {path = "../gravity_utils"} 11 | 12 | deep_space ={git="https://github.com/iqlusioninc/deep_space/", branch="zaki/tendermint_0_21"} 13 | clarity = "0.4.11" 14 | web30 = "0.14.4" 15 | num256 = "0.3" 16 | log = "0.4" 17 | sha3 = "0.9" 18 | -------------------------------------------------------------------------------- /orchestrator/ethereum_gravity/src/deploy_erc20.rs: -------------------------------------------------------------------------------- 1 | //! The Gravity deployERC20 endpoint deploys an ERC20 contract representing a Cosmos asset onto the Ethereum blockchain 2 | //! the event for this deployment is then ferried over to Cosmos where the validators will accept the ERC20 contract address 3 | //! as the representation of this asset on Ethereum 4 | 5 | use clarity::{ 6 | abi::{encode_call, Token}, 7 | Uint256, 8 | }; 9 | use clarity::{Address, PrivateKey}; 10 | use gravity_utils::error::GravityError; 11 | use std::time::Duration; 12 | use web30::{client::Web3, types::SendTxOption}; 13 | 14 | /// Calls the Gravity ethereum contract to deploy the ERC20 representation of the given Cosmos asset 15 | /// denom. If an existing contract is already deployed representing this asset this call will cost 16 | /// Gas but not actually do anything. Returns the new contract address or an error 17 | #[allow(clippy::too_many_arguments)] 18 | pub async fn deploy_erc20( 19 | cosmos_denom: String, 20 | erc20_name: String, 21 | erc20_symbol: String, 22 | decimals: u8, 23 | gravity_contract: Address, 24 | web3: &Web3, 25 | wait_timeout: Option, 26 | sender_secret: PrivateKey, 27 | options: Vec, 28 | ) -> Result { 29 | let sender_address = sender_secret.to_public_key().unwrap(); 30 | let tx_hash = web3 31 | .send_transaction( 32 | gravity_contract, 33 | encode_call( 34 | "deployERC20(string,string,string,uint8)", 35 | &[ 36 | Token::String(cosmos_denom), 37 | Token::String(erc20_name), 38 | Token::String(erc20_symbol), 39 | decimals.into(), 40 | ], 41 | )?, 42 | 0u32.into(), 43 | sender_address, 44 | sender_secret, 45 | options, 46 | ) 47 | .await?; 48 | 49 | if let Some(timeout) = wait_timeout { 50 | web3.wait_for_transaction(tx_hash.clone(), timeout, None) 51 | .await?; 52 | } 53 | 54 | Ok(tx_hash) 55 | } 56 | -------------------------------------------------------------------------------- /orchestrator/ethereum_gravity/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains various components and utilities for interacting with the Gravity Ethereum contract. 2 | 3 | use clarity::Uint256; 4 | 5 | #[macro_use] 6 | extern crate log; 7 | 8 | pub mod deploy_erc20; 9 | pub mod logic_call; 10 | pub mod send_to_cosmos; 11 | pub mod submit_batch; 12 | pub mod utils; 13 | pub mod valset_update; 14 | 15 | pub fn one_eth() -> Uint256 { 16 | 1000000000000000000u128.into() 17 | } 18 | -------------------------------------------------------------------------------- /orchestrator/gorc/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | *.pem -------------------------------------------------------------------------------- /orchestrator/gorc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gorc" 3 | authors = [] 4 | version = "0.1.0" 5 | edition = "2018" 6 | 7 | [dependencies] 8 | gumdrop = "0.7" 9 | serde = { version = "1", features = ["serde_derive"] } 10 | thiserror = "1" 11 | regex = "1.5.4" 12 | 13 | cosmos_gravity = { path = "../cosmos_gravity" } 14 | ethereum_gravity = { path = "../ethereum_gravity" } 15 | gravity_proto = { path = "../gravity_proto" } 16 | gravity_utils = { path = "../gravity_utils" } 17 | orchestrator = { path = "../orchestrator" } 18 | relayer = { path = "../relayer" } 19 | 20 | deep_space ={git="https://github.com/iqlusioninc/deep_space/", branch="zaki/tendermint_0_21"} 21 | clarity = "0.4.12" 22 | actix-rt = "2.2" 23 | rpassword = "5" 24 | bip32 = "0.2" 25 | k256 = { version = "0.9", features = ["pem"] } 26 | pkcs8 = { version = "0.7", features = ["pem"] } 27 | signatory = "0.23.0-pre" 28 | rand_core = { version = "0.6", features = ["std"] } 29 | 30 | abscissa_tokio = { version = "0.6.0-pre.2", features = ["actix"] } 31 | web30 = "0.14" 32 | tokio = "1" 33 | tonic = "0.4" 34 | toml = "0.5" 35 | 36 | prost = "0.7" 37 | bytes = "1" 38 | 39 | [dependencies.abscissa_core] 40 | version = "0.6.0-pre.1" 41 | # optional: use `gimli` to capture backtraces 42 | # see https://github.com/rust-lang/backtrace-rs/issues/189 43 | # features = ["gimli-backtrace"] 44 | 45 | [dev-dependencies] 46 | abscissa_core = { version = "0.6.0-pre.1", features = ["testing"] } 47 | once_cell = "1.2" 48 | -------------------------------------------------------------------------------- /orchestrator/gorc/README.md: -------------------------------------------------------------------------------- 1 | # Gorc 2 | 3 | Gorc is an application. 4 | 5 | ## Getting Started 6 | 7 | This application is authored using [Abscissa], a Rust application framework. 8 | 9 | For more information, see: 10 | 11 | [Documentation] 12 | 13 | [Abscissa]: https://github.com/iqlusioninc/abscissa 14 | [Documentation]: https://docs.rs/abscissa_core/ 15 | -------------------------------------------------------------------------------- /orchestrator/gorc/gorc.toml: -------------------------------------------------------------------------------- 1 | keystore = "/tmp/keystore" 2 | 3 | [gravity] 4 | contract = "0x6b175474e89094c44da98b954eedeac495271d0f" 5 | fees_denom = "stake" 6 | 7 | [ethereum] 8 | key_derivation_path = "m/44'/60'/0'/0/0" 9 | rpc = "http://localhost:8545" 10 | 11 | [cosmos] 12 | gas_price = { amount = 0.001, denom = "stake" } 13 | grpc = "http://localhost:9090" 14 | key_derivation_path = "m/44'/118'/0'/0/0" 15 | prefix = "cosmos" 16 | 17 | [metrics] 18 | listen_addr = "127.0.0.1:3000" 19 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/application.rs: -------------------------------------------------------------------------------- 1 | //! Gorc Abscissa Application 2 | 3 | use crate::{commands::GorcCmd, config::GorcConfig}; 4 | use abscissa_core::{ 5 | application::{self, AppCell}, 6 | config::{self, CfgCell}, 7 | trace, Application, EntryPoint, FrameworkError, StandardPaths, 8 | }; 9 | 10 | /// Application state 11 | pub static APP: AppCell = AppCell::new(); 12 | 13 | /// Gorc Application 14 | #[derive(Debug)] 15 | pub struct GorcApp { 16 | /// Application configuration. 17 | config: CfgCell, 18 | 19 | /// Application state. 20 | state: application::State, 21 | } 22 | 23 | /// Initialize a new application instance. 24 | /// 25 | /// By default no configuration is loaded, and the framework state is 26 | /// initialized to a default, empty state (no components, threads, etc). 27 | impl Default for GorcApp { 28 | fn default() -> Self { 29 | Self { 30 | config: CfgCell::default(), 31 | state: application::State::default(), 32 | } 33 | } 34 | } 35 | 36 | impl Application for GorcApp { 37 | /// Entrypoint command for this application. 38 | type Cmd = EntryPoint; 39 | 40 | /// Application configuration. 41 | type Cfg = GorcConfig; 42 | 43 | /// Paths to resources within the application. 44 | type Paths = StandardPaths; 45 | 46 | /// Accessor for application configuration. 47 | fn config(&self) -> config::Reader { 48 | self.config.read() 49 | } 50 | 51 | /// Borrow the application state immutably. 52 | fn state(&self) -> &application::State { 53 | &self.state 54 | } 55 | 56 | /// Register all components used by this application. 57 | /// 58 | /// If you would like to add additional components to your application 59 | /// beyond the default ones provided by the framework, this is the place 60 | /// to do so. 61 | fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> { 62 | let mut framework_components = self.framework_components(command)?; 63 | let mut app_components = self.state.components_mut(); 64 | framework_components.push(Box::new(abscissa_tokio::TokioComponent::new()?)); 65 | 66 | app_components.register(framework_components) 67 | } 68 | 69 | /// Post-configuration lifecycle callback. 70 | /// 71 | /// Called regardless of whether config is loaded to indicate this is the 72 | /// time in app lifecycle when configuration would be loaded if 73 | /// possible. 74 | fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError> { 75 | // Configure components 76 | let mut components = self.state.components_mut(); 77 | components.after_config(&config)?; 78 | self.config.set_once(config); 79 | Ok(()) 80 | } 81 | 82 | /// Get tracing configuration from command-line options 83 | fn tracing_config(&self, command: &EntryPoint) -> trace::Config { 84 | if command.verbose { 85 | trace::Config::verbose() 86 | } else { 87 | trace::Config::default() 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /orchestrator/gorc/src/bin/gorc/main.rs: -------------------------------------------------------------------------------- 1 | //! Main entry point for Gorc 2 | 3 | #![deny(warnings, missing_docs, trivial_casts, unused_qualifications)] 4 | #![forbid(unsafe_code)] 5 | 6 | use gorc::application::APP; 7 | 8 | /// Boot Gorc 9 | fn main() { 10 | abscissa_core::boot(&APP); 11 | } 12 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands.rs: -------------------------------------------------------------------------------- 1 | //! Gorc Subcommands 2 | //! This is where you specify the subcommands of your application. 3 | 4 | mod cosmos_to_eth; 5 | mod deploy; 6 | mod eth_to_cosmos; 7 | mod keys; 8 | mod orchestrator; 9 | mod print_config; 10 | mod query; 11 | mod sign_delegate_keys; 12 | mod tests; 13 | mod tx; 14 | mod version; 15 | 16 | use crate::config::GorcConfig; 17 | use abscissa_core::{Command, Configurable, Help, Options, Runnable}; 18 | use std::path::PathBuf; 19 | 20 | /// Gorc Configuration Filename 21 | pub const CONFIG_FILE: &str = "gorc.toml"; 22 | 23 | /// Gorc Subcommands 24 | #[derive(Command, Debug, Options, Runnable)] 25 | pub enum GorcCmd { 26 | #[options(help = "Send Cosmos to Ethereum")] 27 | CosmosToEth(cosmos_to_eth::CosmosToEthCmd), 28 | 29 | #[options(help = "tools for contract deployment")] 30 | Deploy(deploy::DeployCmd), 31 | 32 | #[options(help = "Send Ethereum to Cosmos")] 33 | EthToCosmos(eth_to_cosmos::EthToCosmosCmd), 34 | 35 | #[options(help = "get usage information")] 36 | Help(Help), 37 | 38 | #[options(help = "key management commands")] 39 | Keys(keys::KeysCmd), 40 | 41 | #[options(help = "orchestrator management commands")] 42 | Orchestrator(orchestrator::OrchestratorCmd), 43 | 44 | #[options(help = "print config file template")] 45 | PrintConfig(print_config::PrintConfigCmd), 46 | 47 | #[options(help = "query state on either ethereum or cosmos chains")] 48 | Query(query::QueryCmd), 49 | 50 | #[options(help = "sign delegate keys")] 51 | SignDelegateKeys(sign_delegate_keys::SignDelegateKeysCmd), 52 | 53 | #[options(help = "run tests against configured chains")] 54 | Tests(tests::TestsCmd), 55 | 56 | #[options(help = "create transactions on either ethereum or cosmos chains")] 57 | Tx(tx::TxCmd), 58 | 59 | #[options(help = "display version information")] 60 | Version(version::VersionCmd), 61 | } 62 | 63 | /// This trait allows you to define how application configuration is loaded. 64 | impl Configurable for GorcCmd { 65 | /// Location of the configuration file 66 | fn config_path(&self) -> Option { 67 | // Check if the config file exists, and if it does not, ignore it. 68 | // If you'd like for a missing configuration file to be a hard error 69 | // instead, always return `Some(CONFIG_FILE)` here. 70 | let filename = PathBuf::from(CONFIG_FILE); 71 | 72 | if filename.exists() { 73 | Some(filename) 74 | } else { 75 | None 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/deploy.rs: -------------------------------------------------------------------------------- 1 | mod erc20; 2 | use erc20::Erc20; 3 | 4 | use abscissa_core::{Command, Options, Runnable}; 5 | 6 | #[derive(Command, Debug, Options, Runnable)] 7 | pub enum DeployCmd { 8 | #[options( 9 | name = "erc20", 10 | help = "deploy an ERC20 representation of a cosmos denom" 11 | )] 12 | Erc20(Erc20), 13 | } 14 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys.rs: -------------------------------------------------------------------------------- 1 | mod cosmos; 2 | mod eth; 3 | 4 | use abscissa_core::{Command, Options, Runnable}; 5 | 6 | use crate::commands::keys::cosmos::CosmosKeysCmd; 7 | use crate::commands::keys::eth::EthKeysCmd; 8 | 9 | /// `keys` subcommand 10 | /// 11 | /// The `Options` proc macro generates an option parser based on the struct 12 | /// definition, and is defined in the `gumdrop` crate. See their documentation 13 | /// for a more comprehensive example: 14 | /// 15 | /// 16 | #[derive(Command, Debug, Options, Runnable)] 17 | pub enum KeysCmd { 18 | #[options(name = "cosmos")] 19 | CosmosKeysCmd(CosmosKeysCmd), 20 | 21 | #[options(name = "eth")] 22 | EthKeysCmd(EthKeysCmd), 23 | } 24 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/cosmos.rs: -------------------------------------------------------------------------------- 1 | mod add; 2 | mod delete; 3 | mod list; 4 | mod recover; 5 | mod rename; 6 | mod show; 7 | 8 | use abscissa_core::{Command, Options, Runnable}; 9 | 10 | #[derive(Command, Debug, Options, Runnable)] 11 | pub enum CosmosKeysCmd { 12 | #[options(help = "add [name]")] 13 | Add(add::AddCosmosKeyCmd), 14 | 15 | #[options(help = "delete [name]")] 16 | Delete(delete::DeleteCosmosKeyCmd), 17 | 18 | #[options(help = "import [name] (bip39-mnemnoic)")] 19 | Recover(recover::RecoverCosmosKeyCmd), 20 | 21 | #[options(help = "rename [name] [new-name]")] 22 | Rename(rename::RenameCosmosKeyCmd), 23 | 24 | #[options(help = "list")] 25 | List(list::ListCosmosKeyCmd), 26 | 27 | #[options(help = "show [name]")] 28 | Show(show::ShowCosmosKeyCmd), 29 | } 30 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/cosmos/add.rs: -------------------------------------------------------------------------------- 1 | use super::show::ShowCosmosKeyCmd; 2 | use crate::application::APP; 3 | use abscissa_core::{Application, Command, Options, Runnable}; 4 | use k256::pkcs8::ToPrivateKey; 5 | use rand_core::OsRng; 6 | use signatory::FsKeyStore; 7 | use std::path; 8 | 9 | #[derive(Command, Debug, Default, Options)] 10 | pub struct AddCosmosKeyCmd { 11 | #[options(free, help = "add [name]")] 12 | pub args: Vec, 13 | 14 | #[options(help = "overwrite existing key")] 15 | pub overwrite: bool, 16 | } 17 | 18 | // `gorc keys cosmos add [name]` 19 | // - [name] required; key name 20 | impl Runnable for AddCosmosKeyCmd { 21 | fn run(&self) { 22 | let config = APP.config(); 23 | let keystore = path::Path::new(&config.keystore); 24 | let keystore = FsKeyStore::create_or_open(keystore).expect("Could not open keystore"); 25 | 26 | let name = self.args.get(0).expect("name is required"); 27 | let name = name.parse().expect("Could not parse name"); 28 | if let Ok(_info) = keystore.info(&name) { 29 | if !self.overwrite { 30 | eprintln!("Key already exists, exiting."); 31 | return; 32 | } 33 | } 34 | 35 | let mnemonic = bip32::Mnemonic::random(&mut OsRng, Default::default()); 36 | eprintln!("**Important** record this bip39-mnemonic in a safe place:"); 37 | println!("{}", mnemonic.phrase()); 38 | 39 | let seed = mnemonic.to_seed(""); 40 | 41 | let path = config.cosmos.key_derivation_path.clone(); 42 | let path = path 43 | .parse::() 44 | .expect("Could not parse derivation path"); 45 | 46 | let key = bip32::XPrv::derive_from_path(seed, &path).expect("Could not derive key"); 47 | let key = k256::SecretKey::from(key.private_key()); 48 | let key = key 49 | .to_pkcs8_der() 50 | .expect("Could not PKCS8 encod private key"); 51 | 52 | keystore.store(&name, &key).expect("Could not store key"); 53 | 54 | let args = vec![name.to_string()]; 55 | let show_cmd = ShowCosmosKeyCmd { args }; 56 | show_cmd.run(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/cosmos/delete.rs: -------------------------------------------------------------------------------- 1 | use crate::application::APP; 2 | use abscissa_core::{Application, Command, Options, Runnable}; 3 | use signatory::FsKeyStore; 4 | use std::path::Path; 5 | 6 | #[derive(Command, Debug, Default, Options)] 7 | pub struct DeleteCosmosKeyCmd { 8 | #[options(free, help = "delete [name]")] 9 | pub args: Vec, 10 | } 11 | 12 | /// The `gork keys cosmos delete [name] ` subcommand: delete the given key 13 | impl Runnable for DeleteCosmosKeyCmd { 14 | fn run(&self) { 15 | let config = APP.config(); 16 | // Path where key is stored. 17 | let keystore = Path::new(&config.keystore); 18 | let keystore = signatory::FsKeyStore::create_or_open(keystore).unwrap(); 19 | // Collect key name from args. 20 | let name = self.args.get(0).expect("name is required"); 21 | let name = name.parse().expect("Could not parse name"); 22 | // Delete keyname after locating file from path and key name. 23 | let _delete_key = FsKeyStore::delete(&keystore, &name).unwrap(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/cosmos/list.rs: -------------------------------------------------------------------------------- 1 | use super::show::ShowCosmosKeyCmd; 2 | use crate::application::APP; 3 | use abscissa_core::{Application, Command, Options, Runnable}; 4 | use std::path::Path; 5 | 6 | #[derive(Command, Debug, Default, Options)] 7 | pub struct ListCosmosKeyCmd {} 8 | 9 | // Entry point for `gorc keys cosmos list` 10 | impl Runnable for ListCosmosKeyCmd { 11 | fn run(&self) { 12 | let config = APP.config(); 13 | let keystore = Path::new(&config.keystore); 14 | 15 | for entry in keystore.read_dir().expect("Could not read keystore") { 16 | let path = entry.unwrap().path(); 17 | if path.is_file() { 18 | if let Some(extension) = path.extension() { 19 | if extension == "pem" { 20 | let name = path.file_stem().unwrap(); 21 | let name = name.to_str().unwrap(); 22 | let args = vec![name.to_string()]; 23 | let show_cmd = ShowCosmosKeyCmd { args }; 24 | show_cmd.run(); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/cosmos/recover.rs: -------------------------------------------------------------------------------- 1 | use super::show::ShowCosmosKeyCmd; 2 | use crate::application::APP; 3 | use abscissa_core::{Application, Command, Options, Runnable}; 4 | use k256::pkcs8::ToPrivateKey; 5 | use signatory::FsKeyStore; 6 | use std::path; 7 | 8 | #[derive(Command, Debug, Default, Options)] 9 | pub struct RecoverCosmosKeyCmd { 10 | #[options(free, help = "recover [name] (bip39-mnemonic)")] 11 | pub args: Vec, 12 | 13 | #[options(help = "overwrite existing key")] 14 | pub overwrite: bool, 15 | } 16 | 17 | // `gorc keys cosmos recover [name] (bip39-mnemonic)` 18 | // - [name] required; key name 19 | // - (bip39-mnemonic) optional; when absent the user will be prompted to enter it 20 | impl Runnable for RecoverCosmosKeyCmd { 21 | fn run(&self) { 22 | let config = APP.config(); 23 | let keystore = path::Path::new(&config.keystore); 24 | let keystore = 25 | FsKeyStore::create_or_open(keystore).expect("Could not open keystore"); 26 | 27 | let name = self.args.get(0).expect("name is required"); 28 | let name = name.parse().expect("Could not parse name"); 29 | if let Ok(_info) = keystore.info(&name) { 30 | if !self.overwrite { 31 | eprintln!("Key already exists, exiting."); 32 | return; 33 | } 34 | } 35 | 36 | let mnemonic = match self.args.get(1) { 37 | Some(mnemonic) => mnemonic.clone(), 38 | None => rpassword::read_password_from_tty(Some( 39 | "> Enter your bip39-mnemonic:\n", 40 | )) 41 | .expect("Could not read mnemonic"), 42 | }; 43 | 44 | let mnemonic = bip32::Mnemonic::new(mnemonic.trim(), Default::default()) 45 | .expect("Could not parse mnemonic"); 46 | 47 | let seed = mnemonic.to_seed(""); 48 | 49 | let path = config.cosmos.key_derivation_path.clone(); 50 | let path = path 51 | .parse::() 52 | .expect("Could not parse derivation path"); 53 | 54 | let key = bip32::XPrv::derive_from_path(seed, &path).expect("Could not derive key"); 55 | let key = k256::SecretKey::from(key.private_key()); 56 | let key = key 57 | .to_pkcs8_der() 58 | .expect("Could not PKCS8 encod private key"); 59 | 60 | keystore.store(&name, &key).expect("Could not store key"); 61 | 62 | let args = vec![name.to_string()]; 63 | let show_cmd = ShowCosmosKeyCmd { args }; 64 | show_cmd.run(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/cosmos/rename.rs: -------------------------------------------------------------------------------- 1 | use abscissa_core::{Application, Command, Options, Runnable}; 2 | use crate::application::APP; 3 | use std::path; 4 | 5 | #[derive(Command, Debug, Default, Options)] 6 | pub struct RenameCosmosKeyCmd { 7 | #[options(free, help = "rename [name] [new_name]")] 8 | pub args: Vec, 9 | 10 | #[options(help = "overwrite existing key")] 11 | pub overwrite: bool, 12 | } 13 | 14 | /// The `gorc keys cosmos rename [name] [new-name]` subcommand: show keys 15 | impl Runnable for RenameCosmosKeyCmd { 16 | fn run(&self) { 17 | let config = APP.config(); 18 | let keystore = path::Path::new(&config.keystore); 19 | let keystore = signatory::FsKeyStore::create_or_open(keystore).unwrap(); 20 | 21 | let name = self.args.get(0).expect("name is required"); 22 | let name = name.parse().expect("Could not parse name"); 23 | 24 | let new_name = self.args.get(1).expect("new_name is required"); 25 | let new_name = new_name.parse().expect("Could not parse new_name"); 26 | if let Ok(_info) = keystore.info(&new_name) { 27 | if !self.overwrite { 28 | println!("Key already exists, exiting."); 29 | return; 30 | } 31 | } 32 | 33 | let key = keystore.load(&name).expect("Could not load key"); 34 | keystore.store(&new_name, &key).unwrap(); 35 | keystore.delete(&name).unwrap(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/cosmos/show.rs: -------------------------------------------------------------------------------- 1 | use crate::application::APP; 2 | use abscissa_core::{Application, Command, Options, Runnable}; 3 | 4 | #[derive(Command, Debug, Default, Options)] 5 | pub struct ShowCosmosKeyCmd { 6 | #[options(free, help = "show [name]")] 7 | pub args: Vec, 8 | } 9 | 10 | // Entry point for `gorc keys cosmos show [name]` 11 | impl Runnable for ShowCosmosKeyCmd { 12 | fn run(&self) { 13 | let config = APP.config(); 14 | let name = self.args.get(0).expect("name is required"); 15 | let key = config.load_deep_space_key(name.clone()); 16 | 17 | let address = key 18 | .to_address(config.cosmos.prefix.trim()) 19 | .expect("Could not generate public key"); 20 | 21 | println!("{}\t{}", name, address) 22 | } 23 | } -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/eth.rs: -------------------------------------------------------------------------------- 1 | mod add; 2 | mod delete; 3 | mod import; 4 | mod list; 5 | mod recover; 6 | mod rename; 7 | mod show; 8 | 9 | use abscissa_core::{Command, Options, Runnable}; 10 | 11 | #[derive(Command, Debug, Options, Runnable)] 12 | pub enum EthKeysCmd { 13 | #[options(help = "add [name]")] 14 | Add(add::AddEthKeyCmd), 15 | 16 | #[options(help = "delete [name]")] 17 | Delete(delete::DeleteEthKeyCmd), 18 | 19 | #[options(help = "import [name] (private-key)")] 20 | Import(import::ImportEthKeyCmd), 21 | 22 | #[options(help = "list")] 23 | List(list::ListEthKeyCmd), 24 | 25 | #[options(help = "recover [name] (bip39-mnemonic)")] 26 | Recover(recover::RecoverEthKeyCmd), 27 | 28 | #[options(help = "rename [name] [new-name]")] 29 | Rename(rename::RenameEthKeyCmd), 30 | 31 | #[options(help = "show [name]")] 32 | Show(show::ShowEthKeyCmd), 33 | } 34 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/eth/add.rs: -------------------------------------------------------------------------------- 1 | use super::show::ShowEthKeyCmd; 2 | use crate::application::APP; 3 | use abscissa_core::{Application, Command, Options, Runnable}; 4 | use k256::pkcs8::ToPrivateKey; 5 | use rand_core::OsRng; 6 | use signatory::FsKeyStore; 7 | use std::path; 8 | 9 | #[derive(Command, Debug, Default, Options)] 10 | pub struct AddEthKeyCmd { 11 | #[options(free, help = "add [name]")] 12 | pub args: Vec, 13 | 14 | #[options(help = "overwrite existing key")] 15 | pub overwrite: bool, 16 | 17 | #[options(help = "show private key")] 18 | show_private_key: bool, 19 | } 20 | 21 | // Entry point for `gorc keys eth add [name]` 22 | // - [name] required; key name 23 | impl Runnable for AddEthKeyCmd { 24 | fn run(&self) { 25 | let config = APP.config(); 26 | let keystore = path::Path::new(&config.keystore); 27 | let keystore = FsKeyStore::create_or_open(keystore).expect("Could not open keystore"); 28 | 29 | let name = self.args.get(0).expect("name is required"); 30 | let name = name.parse().expect("Could not parse name"); 31 | if let Ok(_info) = keystore.info(&name) { 32 | if !self.overwrite { 33 | eprintln!("Key already exists, exiting."); 34 | return; 35 | } 36 | } 37 | 38 | let mnemonic = bip32::Mnemonic::random(&mut OsRng, Default::default()); 39 | eprintln!("**Important** record this bip39-mnemonic in a safe place:"); 40 | println!("{}", mnemonic.phrase()); 41 | 42 | let seed = mnemonic.to_seed(""); 43 | 44 | let path = config.ethereum.key_derivation_path.trim(); 45 | let path = path 46 | .parse::() 47 | .expect("Could not parse derivation path"); 48 | 49 | let key = bip32::XPrv::derive_from_path(seed, &path).expect("Could not derive key"); 50 | let key = k256::SecretKey::from(key.private_key()); 51 | let key = key 52 | .to_pkcs8_der() 53 | .expect("Could not PKCS8 encod private key"); 54 | 55 | keystore.store(&name, &key).expect("Could not store key"); 56 | 57 | let show_cmd = ShowEthKeyCmd { 58 | args: vec![name.to_string()], 59 | show_private_key: self.show_private_key, 60 | show_name: false, 61 | }; 62 | show_cmd.run(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/eth/delete.rs: -------------------------------------------------------------------------------- 1 | use crate::application::APP; 2 | use abscissa_core::{Application, Command, Options, Runnable}; 3 | use signatory::FsKeyStore; 4 | use std::path; 5 | 6 | #[derive(Command, Debug, Default, Options)] 7 | pub struct DeleteEthKeyCmd { 8 | #[options(free, help = "delete [name]")] 9 | pub args: Vec, 10 | } 11 | 12 | // Entry point for `gorc keys eth delete [name]` 13 | // - [name] required; key name 14 | impl Runnable for DeleteEthKeyCmd { 15 | fn run(&self) { 16 | let config = APP.config(); 17 | let keystore = path::Path::new(&config.keystore); 18 | let keystore = FsKeyStore::create_or_open(keystore).expect("Could not open keystore"); 19 | 20 | let name = self.args.get(0).expect("name is required"); 21 | let name = name.parse().expect("Could not parse name"); 22 | keystore.delete(&name).expect("Could not delete key"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/eth/import.rs: -------------------------------------------------------------------------------- 1 | use super::show::ShowEthKeyCmd; 2 | use crate::application::APP; 3 | use abscissa_core::{Application, Command, Options, Runnable}; 4 | use k256::{pkcs8::ToPrivateKey, SecretKey}; 5 | 6 | use signatory::FsKeyStore; 7 | use std::path; 8 | 9 | #[derive(Command, Debug, Default, Options)] 10 | pub struct ImportEthKeyCmd { 11 | #[options(free, help = "import [name] (private-key)")] 12 | pub args: Vec, 13 | 14 | #[options(help = "overwrite existing key")] 15 | pub overwrite: bool, 16 | 17 | #[options(help = "show private key")] 18 | show_private_key: bool, 19 | } 20 | 21 | // Entry point for `gorc keys eth import [name] (private-key)` 22 | // - [name] required; key name 23 | // - (private-key) optional; when absent the user will be prompted to enter it 24 | impl Runnable for ImportEthKeyCmd { 25 | fn run(&self) { 26 | let config = APP.config(); 27 | let keystore = path::Path::new(&config.keystore); 28 | let keystore = FsKeyStore::create_or_open(keystore).expect("Could not open keystore"); 29 | 30 | let name = self.args.get(0).expect("name is required"); 31 | let name = name.parse().expect("Could not parse name"); 32 | if let Ok(_info) = keystore.info(&name) { 33 | if !self.overwrite { 34 | eprintln!("Key already exists, exiting."); 35 | return; 36 | } 37 | } 38 | 39 | let key = match self.args.get(1) { 40 | Some(private_key) => private_key.clone(), 41 | None => rpassword::read_password_from_tty(Some("> Enter your private-key:\n")) 42 | .expect("Could not read private-key"), 43 | }; 44 | 45 | let key = key 46 | .parse::() 47 | .expect("Could not parse private-key"); 48 | 49 | let key = SecretKey::from_bytes(key.to_bytes()).expect("Could not convert private-key"); 50 | 51 | let key = key 52 | .to_pkcs8_der() 53 | .expect("Could not PKCS8 encod private key"); 54 | 55 | keystore.store(&name, &key).expect("Could not store key"); 56 | 57 | let show_cmd = ShowEthKeyCmd { 58 | args: vec![name.to_string()], 59 | show_private_key: self.show_private_key, 60 | show_name: false, 61 | }; 62 | show_cmd.run(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/eth/list.rs: -------------------------------------------------------------------------------- 1 | use super::show::ShowEthKeyCmd; 2 | use crate::application::APP; 3 | use abscissa_core::{Application, Command, Options, Runnable}; 4 | use std::path; 5 | 6 | #[derive(Command, Debug, Default, Options)] 7 | pub struct ListEthKeyCmd { 8 | #[options(help = "show private key")] 9 | pub show_private_key: bool, 10 | } 11 | 12 | // Entry point for `gorc keys eth list` 13 | impl Runnable for ListEthKeyCmd { 14 | fn run(&self) { 15 | let config = APP.config(); 16 | let keystore = path::Path::new(&config.keystore); 17 | 18 | for entry in keystore.read_dir().expect("Could not read keystore") { 19 | let path = entry.unwrap().path(); 20 | if path.is_file() { 21 | if let Some(extension) = path.extension() { 22 | if extension == "pem" { 23 | let name = path.file_stem().unwrap(); 24 | let name = name.to_str().unwrap(); 25 | let show_cmd = ShowEthKeyCmd { 26 | args: vec![name.to_string()], 27 | show_private_key: self.show_private_key, 28 | show_name: true, 29 | }; 30 | show_cmd.run(); 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/eth/recover.rs: -------------------------------------------------------------------------------- 1 | use super::show::ShowEthKeyCmd; 2 | use crate::application::APP; 3 | use abscissa_core::{Application, Command, Options, Runnable}; 4 | use k256::pkcs8::ToPrivateKey; 5 | use signatory::FsKeyStore; 6 | use std::path; 7 | 8 | #[derive(Command, Debug, Default, Options)] 9 | pub struct RecoverEthKeyCmd { 10 | #[options(free, help = "recover [name] (bip39-mnemonic)")] 11 | pub args: Vec, 12 | 13 | #[options(help = "overwrite existing key")] 14 | pub overwrite: bool, 15 | 16 | #[options(help = "show private key")] 17 | show_private_key: bool, 18 | } 19 | 20 | // Entry point for `gorc keys eth recover [name] (bip39-mnemonic)` 21 | // - [name] required; key name 22 | // - (bip39-mnemonic) optional; when absent the user will be prompted to enter it 23 | impl Runnable for RecoverEthKeyCmd { 24 | fn run(&self) { 25 | let config = APP.config(); 26 | let keystore = path::Path::new(&config.keystore); 27 | let keystore = 28 | FsKeyStore::create_or_open(keystore).expect("Could not open keystore"); 29 | 30 | let name = self.args.get(0).expect("name is required"); 31 | let name = name.parse().expect("Could not parse name"); 32 | if let Ok(_info) = keystore.info(&name) { 33 | if !self.overwrite { 34 | eprintln!("Key already exists, exiting."); 35 | return; 36 | } 37 | } 38 | 39 | let mnemonic = match self.args.get(1) { 40 | Some(mnemonic) => mnemonic.clone(), 41 | None => rpassword::read_password_from_tty(Some( 42 | "> Enter your bip39-mnemonic:\n", 43 | )) 44 | .expect("Could not read mnemonic"), 45 | }; 46 | 47 | let mnemonic = bip32::Mnemonic::new(mnemonic.trim(), Default::default()) 48 | .expect("Could not parse mnemonic"); 49 | 50 | let seed = mnemonic.to_seed(""); 51 | 52 | let path = config.ethereum.key_derivation_path.trim(); 53 | let path = path 54 | .parse::() 55 | .expect("Could not parse derivation path"); 56 | 57 | let key = bip32::XPrv::derive_from_path(seed, &path).expect("Could not derive key"); 58 | let key = k256::SecretKey::from(key.private_key()); 59 | let key = key 60 | .to_pkcs8_der() 61 | .expect("Could not PKCS8 encod private key"); 62 | 63 | keystore.store(&name, &key).expect("Could not store key"); 64 | 65 | let show_cmd = ShowEthKeyCmd { 66 | args: vec![name.to_string()], 67 | show_private_key: self.show_private_key, 68 | show_name: false, 69 | }; 70 | show_cmd.run(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/eth/rename.rs: -------------------------------------------------------------------------------- 1 | use crate::application::APP; 2 | use abscissa_core::{Application, Command, Options, Runnable}; 3 | use signatory::FsKeyStore; 4 | use std::path; 5 | 6 | #[derive(Command, Debug, Default, Options)] 7 | pub struct RenameEthKeyCmd { 8 | #[options(free, help = "rename [name] [new-name]")] 9 | pub args: Vec, 10 | 11 | #[options(help = "overwrite existing key")] 12 | pub overwrite: bool, 13 | } 14 | 15 | // Entry point for `gorc keys eth rename [name] [new-name]` 16 | impl Runnable for RenameEthKeyCmd { 17 | fn run(&self) { 18 | let config = APP.config(); 19 | let keystore = path::Path::new(&config.keystore); 20 | let keystore = FsKeyStore::create_or_open(keystore).expect("Could not open keystore"); 21 | 22 | let name = self.args.get(0).expect("name is required"); 23 | let name = name.parse().expect("Could not parse name"); 24 | 25 | let new_name = self.args.get(1).expect("new-name is required"); 26 | let new_name = new_name.parse().expect("Could not parse new-name"); 27 | if let Ok(_info) = keystore.info(&new_name) { 28 | if !self.overwrite { 29 | eprintln!("Key already exists, exiting."); 30 | return; 31 | } 32 | } 33 | 34 | let key = keystore.load(&name).expect("Could not load key"); 35 | keystore 36 | .store(&new_name, &key) 37 | .expect("Could not store key"); 38 | keystore.delete(&name).expect("Could not delete key"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/keys/eth/show.rs: -------------------------------------------------------------------------------- 1 | use crate::application::APP; 2 | use abscissa_core::{Application, Command, Options, Runnable}; 3 | 4 | #[derive(Command, Debug, Default, Options)] 5 | pub struct ShowEthKeyCmd { 6 | #[options(free, help = "show [name]")] 7 | pub args: Vec, 8 | 9 | #[options(help = "show private key")] 10 | pub show_private_key: bool, 11 | 12 | pub show_name: bool, 13 | } 14 | 15 | // Entry point for `gorc keys eth show [name]` 16 | impl Runnable for ShowEthKeyCmd { 17 | fn run(&self) { 18 | let config = APP.config(); 19 | let name = self.args.get(0).expect("name is required"); 20 | let key = config.load_clarity_key(name.clone()); 21 | 22 | let pub_key = key.to_public_key().expect("Could not build public key"); 23 | 24 | if self.show_name { 25 | print!("{}\t", name); 26 | } 27 | 28 | if self.show_private_key { 29 | println!("{}\t{}", pub_key, key); 30 | } else { 31 | println!("{}", pub_key); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/orchestrator.rs: -------------------------------------------------------------------------------- 1 | mod start; 2 | 3 | use abscissa_core::{Command, Options, Runnable}; 4 | 5 | #[derive(Command, Debug, Options, Runnable)] 6 | pub enum OrchestratorCmd { 7 | #[options(help = "start the orchestrator")] 8 | Start(start::StartCommand), 9 | } 10 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/print_config.rs: -------------------------------------------------------------------------------- 1 | use crate::config::GorcConfig; 2 | use crate::{application::APP, prelude::*}; 3 | use abscissa_core::{Command, Options, Runnable}; 4 | 5 | #[derive(Command, Debug, Default, Options)] 6 | pub struct PrintConfigCmd { 7 | #[options(help = "should default config")] 8 | show_default: bool, 9 | } 10 | 11 | impl Runnable for PrintConfigCmd { 12 | fn run(&self) { 13 | let config = if self.show_default { 14 | GorcConfig::default() 15 | } else { 16 | let config = APP.config(); 17 | GorcConfig { 18 | keystore: config.keystore.to_owned(), 19 | gravity: config.gravity.to_owned(), 20 | ethereum: config.ethereum.to_owned(), 21 | cosmos: config.cosmos.to_owned(), 22 | metrics: config.metrics.to_owned(), 23 | } 24 | }; 25 | 26 | print!("{}", toml::to_string(&config).unwrap()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/query.rs: -------------------------------------------------------------------------------- 1 | //! `query` subcommand 2 | 3 | mod cosmos; 4 | 5 | mod eth; 6 | 7 | use abscissa_core::{Command, Options, Runnable}; 8 | 9 | /// `query` subcommand 10 | /// 11 | /// The `Options` proc macro generates an option parser based on the struct 12 | /// definition, and is defined in the `gumdrop` crate. See their documentation 13 | /// for a more comprehensive example: 14 | /// 15 | /// 16 | #[derive(Command, Debug, Options)] 17 | pub enum QueryCmd { 18 | Cosmos(cosmos::Cosmos), 19 | Eth(eth::Eth), 20 | // Example `--foobar` (with short `-f` argument) 21 | // #[options(short = "f", help = "foobar path"] 22 | // foobar: Option 23 | 24 | // Example `--baz` argument with no short version 25 | // #[options(no_short, help = "baz path")] 26 | // baz: Option 27 | 28 | // "free" arguments don't have an associated flag 29 | // #[options(free)] 30 | // free_args: Vec, 31 | } 32 | 33 | impl Runnable for QueryCmd { 34 | /// Start the application. 35 | fn run(&self) { 36 | // Your code goes here 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/query/cosmos.rs: -------------------------------------------------------------------------------- 1 | //! `cosmos subcommands` subcommand 2 | 3 | use crate::{application::APP, prelude::*}; 4 | use abscissa_core::{Command, Options, Runnable}; 5 | 6 | #[derive(Command, Debug, Options)] 7 | pub enum Cosmos { 8 | #[options(help = "balance [key-name]")] 9 | Balance(Balance), 10 | #[options(help = "gravity-keys [key-name] ")] 11 | GravityKeys(GravityKeys), 12 | } 13 | 14 | impl Runnable for Cosmos { 15 | /// Start the application. 16 | fn run(&self) { 17 | // Your code goes here 18 | } 19 | } 20 | 21 | #[derive(Command, Debug, Options)] 22 | pub struct Balance { 23 | #[options(free)] 24 | free: Vec, 25 | 26 | #[options(help = "print help message")] 27 | help: bool, 28 | } 29 | 30 | impl Runnable for Balance { 31 | fn run(&self) { 32 | assert!(self.free.len() == 1); 33 | let _key_name = self.free[0].clone(); 34 | } 35 | } 36 | 37 | #[derive(Command, Debug, Options)] 38 | pub struct GravityKeys { 39 | #[options(free)] 40 | free: Vec, 41 | 42 | #[options(help = "print help message")] 43 | help: bool, 44 | } 45 | 46 | impl Runnable for GravityKeys { 47 | /// Start the application. 48 | fn run(&self) { 49 | assert!(self.free.len() == 1); 50 | let _key_name = self.free[0].clone(); 51 | 52 | abscissa_tokio::run(&APP, async { unimplemented!() }).unwrap_or_else(|e| { 53 | status_err!("executor exited with error: {}", e); 54 | std::process::exit(1); 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/query/eth.rs: -------------------------------------------------------------------------------- 1 | //! `eth subcommands` subcommand 2 | 3 | use crate::{application::APP, prelude::*}; 4 | use abscissa_core::{Command, Options, Runnable}; 5 | 6 | #[derive(Command, Debug, Options)] 7 | pub enum Eth { 8 | #[options(help = "balance [key-name]")] 9 | Balance(Balance), 10 | 11 | #[options(help = "contract")] 12 | Contract(Contract), 13 | } 14 | 15 | impl Runnable for Eth { 16 | /// Start the application. 17 | fn run(&self) { 18 | // Your code goes here 19 | } 20 | } 21 | 22 | #[derive(Command, Debug, Options)] 23 | pub struct Balance { 24 | #[options(free)] 25 | free: Vec, 26 | 27 | #[options(help = "print help message")] 28 | help: bool, 29 | } 30 | 31 | impl Runnable for Balance { 32 | fn run(&self) { 33 | assert!(self.free.len() == 1); 34 | let _key_name = self.free[0].clone(); 35 | 36 | abscissa_tokio::run(&APP, async { unimplemented!() }).unwrap_or_else(|e| { 37 | status_err!("executor exited with error: {}", e); 38 | std::process::exit(1); 39 | }); 40 | } 41 | } 42 | 43 | #[derive(Command, Debug, Options)] 44 | pub struct Contract { 45 | #[options(help = "print help message")] 46 | help: bool, 47 | } 48 | 49 | impl Runnable for Contract { 50 | /// Start the application. 51 | fn run(&self) {} 52 | } 53 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/sign_delegate_keys.rs: -------------------------------------------------------------------------------- 1 | use crate::{application::APP, prelude::*}; 2 | use abscissa_core::{Application, Command, Options, Runnable}; 3 | use gravity_proto::gravity as proto; 4 | use std::time::Duration; 5 | 6 | #[derive(Command, Debug, Default, Options)] 7 | pub struct SignDelegateKeysCmd { 8 | #[options( 9 | free, 10 | help = "sign-delegate-key [ethereum-key-name] [validator-address] (nonce)" 11 | )] 12 | pub args: Vec, 13 | } 14 | 15 | impl Runnable for SignDelegateKeysCmd { 16 | fn run(&self) { 17 | let config = APP.config(); 18 | abscissa_tokio::run_with_actix(&APP, async { 19 | let name = self.args.get(0).expect("ethereum-key-name is required"); 20 | let key = config.load_clarity_key(name.clone()); 21 | 22 | let val = self.args.get(1).expect("validator-address is required"); 23 | let address = val.parse().expect("Could not parse address"); 24 | 25 | let nonce: u64 = match self.args.get(2) { 26 | Some(nonce) => nonce.parse().expect("cannot parse nonce"), 27 | None => { 28 | let timeout = Duration::from_secs(10); 29 | let contact = deep_space::Contact::new( 30 | &config.cosmos.grpc, 31 | timeout, 32 | &config.cosmos.prefix, 33 | ) 34 | .expect("Could not create contact"); 35 | 36 | let account_info = contact.get_account_info(address).await; 37 | let account_info = account_info.expect("Did not receive account info"); 38 | account_info.sequence 39 | } 40 | }; 41 | 42 | let msg = proto::DelegateKeysSignMsg { 43 | validator_address: val.clone(), 44 | nonce, 45 | }; 46 | 47 | let size = prost::Message::encoded_len(&msg); 48 | let mut buf = bytes::BytesMut::with_capacity(size); 49 | prost::Message::encode(&msg, &mut buf).expect("Failed to encode DelegateKeysSignMsg!"); 50 | 51 | let signature = key.sign_ethereum_msg(&buf); 52 | 53 | println!("{}", signature); 54 | }) 55 | .unwrap_or_else(|e| { 56 | status_err!("executor exited with error: {}", e); 57 | std::process::exit(1); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/tests.rs: -------------------------------------------------------------------------------- 1 | //! `tests` subcommand 2 | 3 | use abscissa_core::{Command, Options, Runnable}; 4 | 5 | #[derive(Command, Debug, Options)] 6 | pub enum TestsCmd { 7 | #[options(help = "runner")] 8 | Runner(Runner), 9 | } 10 | 11 | impl Runnable for TestsCmd { 12 | /// Start the application. 13 | fn run(&self) { 14 | // Your code goes here 15 | } 16 | } 17 | 18 | #[derive(Command, Debug, Options)] 19 | pub struct Runner { 20 | #[options(free)] 21 | free: Vec, 22 | 23 | #[options(help = "print help message")] 24 | help: bool, 25 | } 26 | 27 | impl Runnable for Runner { 28 | fn run(&self) {} 29 | } 30 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/tx.rs: -------------------------------------------------------------------------------- 1 | //! `tx` subcommand 2 | 3 | mod cosmos; 4 | 5 | mod eth; 6 | 7 | use abscissa_core::{Command, Options, Runnable}; 8 | 9 | /// `tx` subcommand 10 | /// 11 | /// The `Options` proc macro generates an option parser based on the struct 12 | /// definition, and is defined in the `gumdrop` crate. See their documentation 13 | /// for a more comprehensive example: 14 | /// 15 | /// 16 | #[derive(Command, Debug, Options)] 17 | pub enum TxCmd { 18 | Cosmos(cosmos::Cosmos), 19 | Eth(eth::Eth), 20 | } 21 | 22 | impl Runnable for TxCmd { 23 | /// Start the application. 24 | fn run(&self) { 25 | // Your code goes here 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/commands/version.rs: -------------------------------------------------------------------------------- 1 | //! `version` subcommand 2 | 3 | #![allow(clippy::never_loop)] 4 | 5 | use super::GorcCmd; 6 | use abscissa_core::{Command, Options, Runnable}; 7 | 8 | /// `version` subcommand 9 | #[derive(Command, Debug, Default, Options)] 10 | pub struct VersionCmd {} 11 | 12 | impl Runnable for VersionCmd { 13 | /// Print version message 14 | fn run(&self) { 15 | println!("{} {}", GorcCmd::name(), GorcCmd::version()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types 2 | 3 | use abscissa_core::error::{BoxError, Context}; 4 | use std::{ 5 | fmt::{self, Display}, 6 | io, 7 | ops::Deref, 8 | }; 9 | use thiserror::Error; 10 | 11 | /// Kinds of errors 12 | #[derive(Copy, Clone, Debug, Eq, Error, PartialEq)] 13 | pub enum ErrorKind { 14 | /// Error in configuration file 15 | #[error("config error")] 16 | Config, 17 | 18 | /// Input/output error 19 | #[error("I/O error")] 20 | Io, 21 | } 22 | 23 | impl ErrorKind { 24 | /// Create an error context from this error 25 | pub fn context(self, source: impl Into) -> Context { 26 | Context::new(self, Some(source.into())) 27 | } 28 | } 29 | 30 | /// Error type 31 | #[derive(Debug)] 32 | pub struct Error(Box>); 33 | 34 | impl Deref for Error { 35 | type Target = Context; 36 | 37 | fn deref(&self) -> &Context { 38 | &self.0 39 | } 40 | } 41 | 42 | impl Display for Error { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | self.0.fmt(f) 45 | } 46 | } 47 | 48 | impl std::error::Error for Error { 49 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 50 | self.0.source() 51 | } 52 | } 53 | 54 | impl From for Error { 55 | fn from(kind: ErrorKind) -> Self { 56 | Context::new(kind, None).into() 57 | } 58 | } 59 | 60 | impl From> for Error { 61 | fn from(context: Context) -> Self { 62 | Error(Box::new(context)) 63 | } 64 | } 65 | 66 | impl From for Error { 67 | fn from(err: io::Error) -> Self { 68 | ErrorKind::Io.context(err).into() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Gorc 2 | //! 3 | //! Application based on the [Abscissa] framework. 4 | //! 5 | //! [Abscissa]: https://github.com/iqlusioninc/abscissa 6 | 7 | // Tip: Deny warnings with `RUSTFLAGS="-D warnings"` environment variable in CI 8 | 9 | #![forbid(unsafe_code)] 10 | #![warn( 11 | // missing_docs, 12 | rust_2018_idioms, 13 | trivial_casts, 14 | unused_lifetimes, 15 | unused_qualifications 16 | )] 17 | 18 | pub mod application; 19 | pub mod commands; 20 | pub mod config; 21 | pub mod error; 22 | pub mod prelude; 23 | pub mod utils; 24 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Application-local prelude: conveniently import types/functions/macros 2 | //! which are generally useful and should be available in every module with 3 | //! `use crate::prelude::*; 4 | 5 | /// Abscissa core prelude 6 | pub use abscissa_core::prelude::*; 7 | 8 | /// Application state 9 | pub use crate::application::APP; 10 | -------------------------------------------------------------------------------- /orchestrator/gorc/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{time::Duration}; 2 | 3 | pub const TIMEOUT: Duration = Duration::from_secs(60); -------------------------------------------------------------------------------- /orchestrator/gravity_proto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gravity_proto" 3 | version = "0.1.0" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | prost = "0.7" 11 | prost-types = "0.7" 12 | bytes = "1" 13 | cosmos-sdk-proto = {git="http://github.com/cosmos/cosmos-rust", branch="main"} 14 | tonic = "0.4" -------------------------------------------------------------------------------- /orchestrator/gravity_proto/src/prost/cosmos_proto.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /orchestrator/gravity_utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gravity_utils" 3 | version = "0.1.0" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | gravity_proto = {path = "../gravity_proto/"} 11 | cosmos-sdk-proto = {git="http://github.com/cosmos/cosmos-rust", branch="main"} 12 | deep_space ={git="https://github.com/iqlusioninc/deep_space/", branch="zaki/tendermint_0_21"} 13 | web30 = "0.14.4" 14 | clarity = "0.4.11" 15 | num256 = "0.3" 16 | serde_derive = "1.0" 17 | serde = "1.0" 18 | tokio = "1.4" 19 | tonic = "0.4" 20 | num-bigint = "0.4" 21 | log = "0.4" 22 | url = "2" 23 | sha3 = "0.9" 24 | [dev_dependencies] 25 | rand = "0.8" 26 | actix = "0.11" -------------------------------------------------------------------------------- /orchestrator/gravity_utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate is for common functions and types for the Gravity rust code 2 | 3 | #[macro_use] 4 | extern crate serde_derive; 5 | #[macro_use] 6 | extern crate log; 7 | 8 | pub mod connection_prep; 9 | pub mod error; 10 | pub mod message_signatures; 11 | pub mod types; 12 | -------------------------------------------------------------------------------- /orchestrator/gravity_utils/src/types/logic_call.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::error::GravityError; 3 | use clarity::{Address as EthAddress, Signature as EthSignature}; 4 | 5 | /// the response we get when querying for a valset confirmation 6 | #[derive(Serialize, Deserialize, Debug, Default, Clone)] 7 | pub struct LogicCall { 8 | pub transfers: Vec, 9 | pub fees: Vec, 10 | pub logic_contract_address: EthAddress, 11 | pub payload: Vec, 12 | pub timeout: u64, 13 | pub invalidation_id: Vec, 14 | pub invalidation_nonce: u64, 15 | } 16 | 17 | impl LogicCall { 18 | pub fn from_proto(input: gravity_proto::gravity::ContractCallTx) -> Result { 19 | let mut transfers: Vec = Vec::new(); 20 | let mut fees: Vec = Vec::new(); 21 | for token in input.tokens { 22 | transfers.push(Erc20Token::from_proto(token)?) 23 | } 24 | for fee in input.fees { 25 | fees.push(Erc20Token::from_proto(fee)?) 26 | } 27 | if transfers.is_empty() || fees.is_empty() { 28 | return Err(GravityError::InvalidBridgeStateError( 29 | "Transaction batch containing no transactions!".to_string(), 30 | )); 31 | } 32 | 33 | Ok(LogicCall { 34 | transfers, 35 | fees, 36 | logic_contract_address: input.address.parse()?, 37 | payload: input.payload, 38 | timeout: input.timeout, 39 | invalidation_id: input.invalidation_scope, 40 | invalidation_nonce: input.invalidation_nonce, 41 | }) 42 | } 43 | } 44 | 45 | /// the response we get when querying for a logic call confirmation 46 | #[derive(Serialize, Deserialize, Debug, Clone)] 47 | pub struct LogicCallConfirmResponse { 48 | pub invalidation_id: Vec, 49 | pub invalidation_nonce: u64, 50 | pub ethereum_signer: EthAddress, 51 | pub eth_signature: EthSignature, 52 | } 53 | 54 | impl LogicCallConfirmResponse { 55 | pub fn from_proto( 56 | input: gravity_proto::gravity::ContractCallTxConfirmation, 57 | ) -> Result { 58 | Ok(LogicCallConfirmResponse { 59 | invalidation_id: input.invalidation_scope, 60 | invalidation_nonce: input.invalidation_nonce, 61 | ethereum_signer: input.ethereum_signer.parse()?, 62 | eth_signature: EthSignature::from_bytes(&input.signature)?, 63 | }) 64 | } 65 | } 66 | 67 | impl Confirm for LogicCallConfirmResponse { 68 | fn get_eth_address(&self) -> EthAddress { 69 | self.ethereum_signer 70 | } 71 | fn get_signature(&self) -> EthSignature { 72 | self.eth_signature.clone() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /orchestrator/gravity_utils/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | use clarity::Address as EthAddress; 2 | use num256::Uint256; 3 | mod batches; 4 | mod ethereum_events; 5 | mod logic_call; 6 | mod signatures; 7 | mod valsets; 8 | use crate::error::GravityError; 9 | 10 | pub use batches::*; 11 | pub use ethereum_events::*; 12 | pub use logic_call::*; 13 | pub use signatures::*; 14 | pub use valsets::*; 15 | 16 | #[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq, Hash)] 17 | pub struct Erc20Token { 18 | pub amount: Uint256, 19 | #[serde(rename = "contract")] 20 | pub token_contract_address: EthAddress, 21 | } 22 | 23 | impl Erc20Token { 24 | pub fn from_proto(input: gravity_proto::gravity::Erc20Token) -> Result { 25 | Ok(Erc20Token { 26 | amount: input.amount.parse()?, 27 | token_contract_address: EthAddress::parse_and_validate(&input.contract)?, 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /orchestrator/orchestrator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "orchestrator" 3 | version = "0.4.1" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "orchestrator" 9 | path = "src/lib.rs" 10 | 11 | [[bin]] 12 | name = "orchestrator" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | relayer = { path = "../relayer" } 17 | ethereum_gravity = { path = "../ethereum_gravity" } 18 | cosmos_gravity = { path = "../cosmos_gravity" } 19 | gravity_utils = { path = "../gravity_utils" } 20 | gravity_proto = { path = "../gravity_proto" } 21 | 22 | deep_space = { git = "https://github.com/iqlusioninc/deep_space/", branch = "zaki/tendermint_0_21" } 23 | serde_derive = "1.0" 24 | clarity = "0.4.11" 25 | docopt = "1" 26 | serde = "1.0" 27 | actix-rt = "2.2" 28 | lazy_static = "1" 29 | web30 = "0.14.4" 30 | num256 = "0.3" 31 | log = "0.4" 32 | env_logger = "0.8" 33 | serde_json = "1.0" 34 | tokio = "1.4.0" 35 | rand = "0.8" 36 | tonic = "0.4" 37 | futures = "0.3" 38 | openssl-probe = "0.1" 39 | 40 | axum = "0.1.2" 41 | hyper = "0.14.11" 42 | prometheus = "0.12.0" 43 | 44 | # this is a dirty trick, we depent transitively on OpenSSL it's never 45 | # called directly in this crate, but if we specify this dep we can enable 46 | # this feature for all the crates in our dependency tree which depend on 47 | # this crate. This allows for easy cross compiled builds because the 'vendored' 48 | # feature includes it's own OpenSSL version that's compiled on the fly 49 | # If ANY crate in this workspace has this it will work for all of them. 50 | openssl = { version = "0.10", features = ["vendored"] } 51 | -------------------------------------------------------------------------------- /orchestrator/orchestrator/src/get_with_retry.rs: -------------------------------------------------------------------------------- 1 | //! Basic utility functions to stubbornly get data 2 | use clarity::Uint256; 3 | use cosmos_gravity::query::get_last_event_nonce; 4 | use deep_space::address::Address as CosmosAddress; 5 | use gravity_proto::gravity::query_client::QueryClient as GravityQueryClient; 6 | use std::time::Duration; 7 | use tokio::time::sleep as delay_for; 8 | use tonic::transport::Channel; 9 | use web30::client::Web3; 10 | 11 | pub const RETRY_TIME: Duration = Duration::from_secs(5); 12 | 13 | /// gets the current block number, no matter how long it takes 14 | pub async fn get_block_number_with_retry(web3: &Web3) -> Uint256 { 15 | let mut res = web3.eth_block_number().await; 16 | while res.is_err() { 17 | error!("Failed to get latest block! Is your Eth node working?"); 18 | delay_for(RETRY_TIME).await; 19 | res = web3.eth_block_number().await; 20 | } 21 | res.unwrap() 22 | } 23 | 24 | /// gets the last event nonce, no matter how long it takes. 25 | pub async fn get_last_event_nonce_with_retry( 26 | client: &mut GravityQueryClient, 27 | our_cosmos_address: CosmosAddress, 28 | ) -> u64 { 29 | let mut res = get_last_event_nonce(client, our_cosmos_address).await; 30 | while res.is_err() { 31 | error!( 32 | "Failed to get last event nonce, is the Cosmos GRPC working? {:?}", 33 | res 34 | ); 35 | delay_for(RETRY_TIME).await; 36 | res = get_last_event_nonce(client, our_cosmos_address).await; 37 | } 38 | res.unwrap() 39 | } 40 | 41 | /// gets the net version, no matter how long it takes 42 | pub async fn get_net_version_with_retry(web3: &Web3) -> u64 { 43 | let mut res = web3.net_version().await; 44 | while res.is_err() { 45 | error!("Failed to get net version! Is your Eth node working?"); 46 | delay_for(RETRY_TIME).await; 47 | res = web3.net_version().await; 48 | } 49 | res.unwrap() 50 | } 51 | -------------------------------------------------------------------------------- /orchestrator/orchestrator/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ethereum_event_watcher; 2 | pub mod get_with_retry; 3 | pub mod main_loop; 4 | pub mod metrics; 5 | pub mod oracle_resync; 6 | 7 | #[macro_use] 8 | extern crate log; 9 | extern crate prometheus; -------------------------------------------------------------------------------- /orchestrator/proto_build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proto_build" 3 | version = "0.1.0" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | 8 | [dependencies] 9 | prost = "0.7" 10 | tonic = "0.4" 11 | prost-build = "0.7" 12 | tempdir = "0.3" 13 | walkdir = "2" 14 | tonic-build = "0.4" 15 | regex = "1.5" -------------------------------------------------------------------------------- /orchestrator/register_delegate_keys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "register_delegate_keys" 3 | version = "0.4.1" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "register-delegate-keys" 9 | path = "src/main.rs" 10 | 11 | [dependencies] 12 | relayer = {path = "../relayer/"} 13 | ethereum_gravity = {path = "../ethereum_gravity"} 14 | cosmos_gravity = {path = "../cosmos_gravity"} 15 | gravity_utils = {path = "../gravity_utils"} 16 | gravity_proto = {path = "../gravity_proto/"} 17 | 18 | deep_space ={git="https://github.com/iqlusioninc/deep_space/", branch="zaki/tendermint_0_21"} 19 | contact = "0.4" 20 | serde_derive = "1.0" 21 | clarity = "0.4.11" 22 | docopt = "1" 23 | serde = "1.0" 24 | actix-rt = "2.2.0" 25 | lazy_static = "1" 26 | web30 = "0.14.4" 27 | env_logger = "0.8" 28 | log = "0.4.14" 29 | openssl-probe = "0.1" 30 | rand = "0.8" -------------------------------------------------------------------------------- /orchestrator/relayer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "relayer" 3 | version = "0.4.1" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "relayer" 9 | path = "src/lib.rs" 10 | 11 | [[bin]] 12 | name = "relayer" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | ethereum_gravity = { path = "../ethereum_gravity" } 17 | cosmos_gravity = { path = "../cosmos_gravity" } 18 | gravity_utils = { path = "../gravity_utils" } 19 | gravity_proto = { path = "../gravity_proto" } 20 | 21 | deep_space ={git="https://github.com/iqlusioninc/deep_space/", branch="zaki/tendermint_0_21"} 22 | serde_derive = "1.0" 23 | clarity = "0.4.11" 24 | docopt = "1" 25 | serde = "1.0" 26 | actix-rt = "2" 27 | lazy_static = "1" 28 | web30 = "0.14.4" 29 | num256 = "0.3" 30 | log = "0.4" 31 | env_logger = "0.8" 32 | tokio = "1.4" 33 | tonic = "0.4" 34 | openssl-probe = "0.1" 35 | 36 | 37 | [dev-dependencies] 38 | actix = "0.11" 39 | -------------------------------------------------------------------------------- /orchestrator/relayer/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod batch_relaying; 2 | pub mod find_latest_valset; 3 | pub mod logic_call_relaying; 4 | pub mod main_loop; 5 | pub mod valset_relaying; 6 | 7 | #[macro_use] 8 | extern crate log; 9 | -------------------------------------------------------------------------------- /orchestrator/startup.sh: -------------------------------------------------------------------------------- 1 | #bin/sh 2 | 3 | validator_address=$(getent hosts ${VALIDATOR} | awk '{ print $1 }') 4 | rpc="http://$validator_address:1317" 5 | grpc="http://$validator_address:9090" 6 | ethrpc="http://$(getent hosts ethereum | awk '{ print $1 }'):8545" 7 | 8 | echo orchestrator \ 9 | --address-prefix=cosmos \ 10 | --contract-address="${CONTRACT_ADDR}" \ 11 | --cosmos-grpc="$grpc" \ 12 | --cosmos-phrase="${COSMOS_PHRASE}" \ 13 | --ethereum-key="${ETH_PRIVATE_KEY}" \ 14 | --ethereum-rpc="$ethrpc" \ 15 | --fees="${DENOM}" 16 | 17 | orchestrator \ 18 | --address-prefix=cosmos \ 19 | --contract-address="${CONTRACT_ADDR}" \ 20 | --cosmos-grpc="$grpc" \ 21 | --cosmos-phrase="${COSMOS_PHRASE}" \ 22 | --ethereum-key="${ETH_PRIVATE_KEY}" \ 23 | --ethereum-rpc="$ethrpc" \ 24 | --fees="${DENOM}" -------------------------------------------------------------------------------- /orchestrator/test_runner/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_runner" 3 | version = "0.1.0" 4 | authors = ["Justin Kilpatrick "] 5 | edition = "2018" 6 | 7 | # only becuase I like - more in names 8 | # [[bin]] 9 | # name = "test-runner" 10 | # path = "src/main.rs" 11 | 12 | [dependencies] 13 | ethereum_gravity = {path = "../ethereum_gravity"} 14 | cosmos_gravity = {path = "../cosmos_gravity"} 15 | gravity_utils = {path = "../gravity_utils"} 16 | gravity_proto = {path = "../gravity_proto/"} 17 | orchestrator = {path = "../orchestrator/"} 18 | 19 | deep_space ={git="https://github.com/iqlusioninc/deep_space/", branch="zaki/tendermint_0_21"} 20 | serde_derive = "1.0" 21 | clarity = "0.4.11" 22 | docopt = "1" 23 | serde = "1.0" 24 | actix = "0.11" 25 | actix-web = {version = "3", features=["openssl"]} 26 | actix-rt = "2.2" 27 | lazy_static = "1" 28 | url = "2" 29 | web30 = "0.14.4" 30 | num256 = "0.3" 31 | log = "0.4" 32 | env_logger = "0.8" 33 | tokio = "1.4.0" 34 | rand = "0.8" 35 | tonic = "0.4" 36 | futures = "0.3" 37 | -------------------------------------------------------------------------------- /orchestrator/test_runner/src/arbitrary_logic.rs: -------------------------------------------------------------------------------- 1 | //! This is the testing module for arbitrary logic functionality. This is where instead of managing transfers directly the bridge simply passes an 2 | //! arbitrary call to an arbitrary sub contract along with a specific amount of funds, allowing for execution of whatever command is required 3 | 4 | use crate::TOTAL_TIMEOUT; 5 | use deep_space::Contact; 6 | use gravity_proto::gravity::query_client::QueryClient as GravityQueryClient; 7 | use tokio::time::sleep as delay_for; 8 | use tonic::transport::Channel; 9 | use web30::client::Web3; 10 | 11 | pub async fn arbitrary_logic_test( 12 | _web30: &Web3, 13 | _grpc_client: GravityQueryClient, 14 | _contact: &Contact, 15 | ) { 16 | delay_for(TOTAL_TIMEOUT).await; 17 | } 18 | -------------------------------------------------------------------------------- /orchestrator/test_runner/src/valset_stress.rs: -------------------------------------------------------------------------------- 1 | use crate::happy_path::test_valset_update; 2 | use crate::utils::ValidatorKeys; 3 | use clarity::Address as EthAddress; 4 | use deep_space::Contact; 5 | use web30::client::Web3; 6 | 7 | pub async fn validator_set_stress_test( 8 | web30: &Web3, 9 | contact: &Contact, 10 | keys: Vec, 11 | gravity_address: EthAddress, 12 | ) { 13 | for _ in 0u32..10 { 14 | test_valset_update(&web30, contact, &keys, gravity_address).await; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /orchestrator/test_runner/startup.sh: -------------------------------------------------------------------------------- 1 | validator_address=$(getent hosts gravity0 | awk '{ print $1 }') 2 | abci="http://$validator_address:26657" 3 | grpc="http://$validator_address:9090" 4 | ethrpc="http://$(getent hosts ethereum | awk '{ print $1 }'):8545" 5 | 6 | COSMOS_NODE_GRPC="$grpc" COSMOS_NODE_ABCI="$abci" ETH_NODE="$ethrpc" PATH=$PATH:$HOME/.cargo/bin test_runner -------------------------------------------------------------------------------- /orchestrator/testnet.Dockerfile: -------------------------------------------------------------------------------- 1 | # Reference: https://www.lpalmieri.com/posts/fast-rust-docker-builds/ 2 | 3 | FROM rust:1.52 as cargo-chef-rust 4 | RUN apt-get install bash 5 | RUN cargo install cargo-chef 6 | 7 | FROM cargo-chef-rust as planner 8 | WORKDIR app 9 | # We only pay the installation cost once, 10 | # it will be cached from the second build onwards 11 | # To ensure a reproducible build consider pinning 12 | # the cargo-chef version with `--version X.X.X` 13 | COPY . . 14 | RUN cargo chef prepare --recipe-path recipe.json 15 | 16 | FROM cargo-chef-rust as cacher 17 | WORKDIR app 18 | COPY --from=planner /app/recipe.json recipe.json 19 | RUN cargo chef cook --release --recipe-path recipe.json 20 | 21 | FROM cargo-chef-rust as builder 22 | WORKDIR app 23 | COPY . . 24 | # Copy over the cached dependencies 25 | COPY --from=cacher /app/target target 26 | COPY --from=cacher /usr/local/cargo /usr/local/cargo 27 | RUN cargo build --manifest-path=test_runner/Cargo.toml --release --bin test_runner 28 | 29 | FROM cargo-chef-rust as runtime 30 | WORKDIR app 31 | COPY test_runner/startup.sh startup.sh 32 | COPY --from=builder /app/target/release/test_runner /usr/local/bin 33 | CMD sh startup.sh -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /solidity/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | typechain/ 3 | artifacts/ 4 | cache/ 5 | 6 | .git 7 | .gitignore 8 | .dockerignore 9 | Dockerfile -------------------------------------------------------------------------------- /solidity/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:15.11-alpine3.13 2 | 3 | RUN apk update 4 | RUN apk add --no-cache python3 make g++ curl 5 | 6 | COPY package.json package.json 7 | COPY package-lock.json package-lock.json 8 | RUN npm ci 9 | 10 | COPY . . 11 | RUN npm ci 12 | RUN chmod -R +x scripts 13 | 14 | RUN npm run typechain 15 | 16 | CMD npx ts-node \ 17 | contract-deployer.ts \ 18 | --cosmos-node="http://gravity0:26657" \ 19 | --eth-node="http://ethereum:8545" \ 20 | --eth-privkey="0xb1bab011e03a9862664706fc3bbaa1b16651528e5f0e7fbfcbfdd8be302a13e7" \ 21 | --contract=artifacts/contracts/Gravity.sol/Gravity.json \ 22 | --test-mode=true -------------------------------------------------------------------------------- /solidity/ci.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:edge 2 | 3 | COPY contract-deployer /usr/bin/contract-deployer 4 | COPY contracts contracts 5 | 6 | CMD contract-deployer \ 7 | --cosmos-node="http://gravity0:26657" \ 8 | --eth-node="http://ethereum:8545" \ 9 | --eth-privkey="0xb1bab011e03a9862664706fc3bbaa1b16651528e5f0e7fbfcbfdd8be302a13e7" \ 10 | --contract=artifacts/contracts/Gravity.sol/Gravity.json \ 11 | --test-mode=true -------------------------------------------------------------------------------- /solidity/contracts/CosmosToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 3 | 4 | contract CosmosERC20 is ERC20 { 5 | uint256 MAX_UINT = 2**256 - 1; 6 | 7 | constructor( 8 | address _gravityAddress, 9 | string memory _name, 10 | string memory _symbol, 11 | uint8 _decimals 12 | ) public ERC20(_name, _symbol) { 13 | _setupDecimals(_decimals); 14 | _mint(_gravityAddress, MAX_UINT); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /solidity/contracts/HashingTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | import "hardhat/console.sol"; 4 | import "@openzeppelin/contracts/math/SafeMath.sol"; 5 | 6 | contract HashingTest { 7 | using SafeMath for uint256; 8 | 9 | bytes32 public lastCheckpoint; 10 | address[] public state_validators; 11 | uint256[] public state_powers; 12 | uint256 public state_nonce; 13 | 14 | function IterativeHash( 15 | address[] memory _validators, 16 | uint256[] memory _powers, 17 | uint256 _valsetNonce, 18 | bytes32 _gravityId 19 | ) public { 20 | // bytes32 encoding of the string "checkpoint" 21 | bytes32 methodName = 0x636865636b706f696e7400000000000000000000000000000000000000000000; 22 | 23 | bytes32 checkpoint = keccak256(abi.encode(_gravityId, methodName, _valsetNonce)); 24 | 25 | // Iterative hashing of valset 26 | { 27 | for (uint256 i = 0; i < _validators.length; i = i.add(1)) { 28 | // Check that validator powers are decreasing or equal (this allows the next 29 | // caller to break out of signature evaluation ASAP to save more gas) 30 | if (i != 0) { 31 | require( 32 | !(_powers[i] > _powers[i - 1]), 33 | "Validator power must not be higher than previous validator in batch" 34 | ); 35 | } 36 | checkpoint = keccak256(abi.encode(checkpoint, _validators[i], _powers[i])); 37 | } 38 | } 39 | 40 | lastCheckpoint = checkpoint; 41 | } 42 | 43 | function ConcatHash( 44 | address[] memory _validators, 45 | uint256[] memory _powers, 46 | uint256 _valsetNonce, 47 | bytes32 _gravityId 48 | ) public { 49 | // bytes32 encoding of the string "checkpoint" 50 | bytes32 methodName = 0x636865636b706f696e7400000000000000000000000000000000000000000000; 51 | 52 | bytes32 idHash = keccak256(abi.encode(_gravityId, methodName, _valsetNonce)); 53 | 54 | bytes32 validatorHash = keccak256(abi.encode(_validators)); 55 | 56 | bytes32 powersHash = keccak256(abi.encode(_powers)); 57 | 58 | bytes32 checkpoint = keccak256(abi.encode(idHash, validatorHash, powersHash)); 59 | 60 | lastCheckpoint = checkpoint; 61 | } 62 | 63 | function ConcatHash2( 64 | address[] memory _validators, 65 | uint256[] memory _powers, 66 | uint256 _valsetNonce, 67 | bytes32 _gravityId 68 | ) public { 69 | // bytes32 encoding of the string "checkpoint" 70 | bytes32 methodName = 0x636865636b706f696e7400000000000000000000000000000000000000000000; 71 | 72 | bytes32 checkpoint = keccak256( 73 | abi.encode(_gravityId, methodName, _valsetNonce, _validators, _powers) 74 | ); 75 | 76 | lastCheckpoint = checkpoint; 77 | } 78 | 79 | function JustSaveEverything( 80 | address[] memory _validators, 81 | uint256[] memory _powers, 82 | uint256 _valsetNonce 83 | ) public { 84 | state_validators = _validators; 85 | state_powers = _powers; 86 | state_nonce = _valsetNonce; 87 | } 88 | 89 | function JustSaveEverythingAgain( 90 | address[] memory _validators, 91 | uint256[] memory _powers, 92 | uint256 _valsetNonce 93 | ) public { 94 | state_validators = _validators; 95 | state_powers = _powers; 96 | state_nonce = _valsetNonce; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /solidity/contracts/ReentrantERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 3 | import "./Gravity.sol"; 4 | 5 | pragma experimental ABIEncoderV2; 6 | 7 | // Reentrant evil erc20 8 | contract ReentrantERC20 { 9 | address state_gravityAddress; 10 | 11 | constructor(address _gravityAddress) public { 12 | state_gravityAddress = _gravityAddress; 13 | } 14 | 15 | function transfer(address recipient, uint256 amount) public returns (bool) { 16 | // _currentValidators, _currentPowers, _currentValsetNonce, _v, _r, _s, _args);( 17 | address[] memory addresses = new address[](0); 18 | bytes32[] memory bytes32s = new bytes32[](0); 19 | uint256[] memory uint256s = new uint256[](0); 20 | bytes memory bytess = new bytes(0); 21 | uint256 zero = 0; 22 | LogicCallArgs memory args; 23 | 24 | { 25 | args = LogicCallArgs( 26 | uint256s, 27 | addresses, 28 | uint256s, 29 | addresses, 30 | address(0), 31 | bytess, 32 | zero, 33 | bytes32(0), 34 | zero 35 | ); 36 | } 37 | 38 | Gravity(state_gravityAddress).submitLogicCall( 39 | addresses, 40 | uint256s, 41 | zero, 42 | new uint8[](0), 43 | bytes32s, 44 | bytes32s, 45 | args 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /solidity/contracts/SigningTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | import "hardhat/console.sol"; 4 | 5 | contract SigningTest { 6 | function checkSignature( 7 | address _signer, 8 | bytes32 _theHash, 9 | uint8 _v, 10 | bytes32 _r, 11 | bytes32 _s 12 | ) public view { 13 | bytes32 messageDigest = keccak256(abi.encode("\x19Ethereum Signed Message:\n32", _theHash)); 14 | 15 | console.log("signer"); 16 | console.logAddress(_signer); 17 | console.log("theHash"); 18 | console.logBytes32(_theHash); 19 | console.log("v"); 20 | console.logUint(_v); 21 | console.log("r"); 22 | console.logBytes32(_r); 23 | console.log("s"); 24 | console.logBytes32(_s); 25 | console.log("ecrecover"); 26 | console.logAddress(ecrecover(messageDigest, _v, _r, _s)); 27 | console.log("address"); 28 | console.logAddress(_signer); 29 | 30 | require(_signer == ecrecover(messageDigest, _v, _r, _s), "Signature does not match."); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solidity/contracts/SimpleLogicBatch.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 7 | import "@openzeppelin/contracts/utils/Address.sol"; 8 | 9 | import "hardhat/console.sol"; 10 | 11 | 12 | // This middleware allows arbitrary logic batches, executed by a single 13 | // logic contract taking a single token. 14 | // It would be trivial to pass an array of multiple token contracts and 15 | // an array of multiple logic contracts, but we are not doing it here to 16 | // save gas. 17 | contract SimpleLogicBatchMiddleware is Ownable { 18 | using SafeERC20 for IERC20; 19 | 20 | event LogicCallEvent( 21 | address _logicContract, 22 | address _tokenContract, 23 | bool _success, 24 | bytes _returnData 25 | ); 26 | 27 | function logicBatch( 28 | uint256[] memory _amounts, 29 | bytes[] memory _payloads, 30 | address _logicContract, 31 | address _tokenContract 32 | ) public onlyOwner { 33 | // Send transaction amounts to destinations 34 | console.log("number of _amounts:%s", _amounts.length); 35 | for (uint256 i = 0; i < _amounts.length; i++) { 36 | console.log("Transfering %s",_amounts[i]); 37 | 38 | IERC20(_tokenContract).safeTransfer(_logicContract, _amounts[i]); 39 | bytes memory returnData= Address.functionCall(_logicContract,_payloads[i]); 40 | emit LogicCallEvent(_tokenContract, _logicContract, true, returnData); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /solidity/contracts/TestERC20A.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 3 | 4 | // One of three testing coins 5 | contract TestERC20A is ERC20 { 6 | constructor() public ERC20("Bitcoin MAX", "MAX") { 7 | _mint(0xc783df8a850f42e7F7e57013759C285caa701eB6, 10000); 8 | _mint(0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4, 10000); 9 | _mint(0xE5904695748fe4A84b40b3fc79De2277660BD1D3, 10000); 10 | _mint(0x92561F28Ec438Ee9831D00D1D59fbDC981b762b2, 10000); 11 | _mint(0x2fFd013AaA7B5a7DA93336C2251075202b33FB2B, 10000); 12 | // this is the EtherBase address for our testnet miner in 13 | // tests/assets/ETHGenesis.json so it wil have both a lot 14 | // of ETH and a lot of erc20 tokens to test with 15 | _mint(0xBf660843528035a5A4921534E156a27e64B231fE, 100000000000000000000000000); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /solidity/contracts/TestERC20B.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 3 | 4 | // One of three testing coins 5 | contract TestERC20B is ERC20 { 6 | constructor() public ERC20("2 Ethereum", "E2H") { 7 | _mint(0xc783df8a850f42e7F7e57013759C285caa701eB6, 10000); 8 | _mint(0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4, 10000); 9 | _mint(0xE5904695748fe4A84b40b3fc79De2277660BD1D3, 10000); 10 | _mint(0x92561F28Ec438Ee9831D00D1D59fbDC981b762b2, 10000); 11 | _mint(0x2fFd013AaA7B5a7DA93336C2251075202b33FB2B, 10000); 12 | // this is the EtherBase address for our testnet miner in 13 | // tests/assets/ETHGenesis.json so it wil have both a lot 14 | // of ETH and a lot of erc20 tokens to test with 15 | _mint(0xBf660843528035a5A4921534E156a27e64B231fE, 100000000000000000000000000); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /solidity/contracts/TestERC20C.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 3 | 4 | // One of three testing coins 5 | contract TestERC20C is ERC20 { 6 | constructor() public ERC20("Byecoin", "BYE") { 7 | _mint(0xc783df8a850f42e7F7e57013759C285caa701eB6, 10000); 8 | _mint(0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4, 10000); 9 | _mint(0xE5904695748fe4A84b40b3fc79De2277660BD1D3, 10000); 10 | _mint(0x92561F28Ec438Ee9831D00D1D59fbDC981b762b2, 10000); 11 | _mint(0x2fFd013AaA7B5a7DA93336C2251075202b33FB2B, 10000); 12 | // this is the EtherBase address for our testnet miner in 13 | // tests/assets/ETHGenesis.json so it wil have both a lot 14 | // of ETH and a lot of erc20 tokens to test with 15 | _mint(0xBf660843528035a5A4921534E156a27e64B231fE, 100000000000000000000000000); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /solidity/contracts/TestLogicContract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | import "hardhat/console.sol"; 4 | import "@openzeppelin/contracts/math/SafeMath.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 8 | 9 | contract TestLogicContract is Ownable { 10 | address state_tokenContract; 11 | 12 | constructor(address _tokenContract) public { 13 | state_tokenContract = _tokenContract; 14 | } 15 | 16 | function transferTokens( 17 | address _to, 18 | uint256 _a, 19 | uint256 _b 20 | ) public onlyOwner { 21 | IERC20(state_tokenContract).transfer(_to, _a + _b); 22 | console.log("Sent Tokens"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solidity/contracts/TestTokenBatchMiddleware copy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | import "@openzeppelin/contracts/access/Ownable.sol"; 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 6 | 7 | contract TestTokenBatchMiddleware is Ownable { 8 | using SafeERC20 for IERC20; 9 | 10 | function submitBatch( 11 | uint256[] memory _amounts, 12 | address[] memory _destinations, 13 | address _tokenContract 14 | ) public onlyOwner { 15 | // Send transaction amounts to destinations 16 | for (uint256 i = 0; i < _amounts.length; i++) { 17 | IERC20(_tokenContract).safeTransfer(_destinations[i], _amounts[i]); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /solidity/contracts/TestUniswapLiquidity.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.6; 2 | 3 | import "hardhat/console.sol"; 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; 6 | import "@uniswap/v2-periphery/contracts/libraries/UniswapV2Library.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 8 | 9 | contract TestUniswapLiquidity is Ownable { 10 | address router; 11 | 12 | constructor(address _uni_router) public { 13 | router = _uni_router; 14 | } 15 | 16 | function redeemLiquidityETH( 17 | address token, 18 | uint256 liquidity, 19 | uint256 amountTokenMin, 20 | uint256 amountETHMin, 21 | address to, 22 | uint256 deadline 23 | ) public onlyOwner { 24 | address pair = 25 | UniswapV2Library.pairFor( 26 | IUniswapV2Router02(router).factory(), 27 | token, 28 | IUniswapV2Router02(router).WETH() 29 | ); 30 | IUniswapV2Pair(pair).approve(router, 2**256 - 1); 31 | 32 | IUniswapV2Router02(router).removeLiquidityETH( 33 | token, 34 | liquidity, 35 | amountTokenMin, 36 | amountETHMin, 37 | to, 38 | deadline 39 | ); 40 | } 41 | 42 | function redeemLiquidity( 43 | address tokenA, 44 | address tokenB, 45 | uint256 liquidity, 46 | uint256 amountAMin, 47 | uint256 amountBMin, 48 | address to, 49 | uint256 deadline 50 | ) public onlyOwner { 51 | IUniswapV2Router02(router).removeLiquidity( 52 | tokenA, 53 | tokenB, 54 | liquidity, 55 | amountAMin, 56 | amountBMin, 57 | to, 58 | deadline 59 | ); 60 | } 61 | 62 | function transferTokens( 63 | address _to, 64 | uint256 _a, 65 | uint256 _b, 66 | address state_tokenContract 67 | ) public onlyOwner { 68 | IERC20(state_tokenContract).transfer(_to, _a + _b); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /solidity/debug-logs.ts: -------------------------------------------------------------------------------- 1 | import { Gravity } from "./typechain/Gravity"; 2 | import { ethers } from "ethers"; 3 | import fs from "fs"; 4 | 5 | function getContractArtifacts(path: string): { bytecode: string; abi: string } { 6 | var { bytecode, abi } = JSON.parse(fs.readFileSync(path, "utf8").toString()); 7 | return { bytecode, abi }; 8 | } 9 | 10 | 11 | (async function () { 12 | const ethNode = "http://localhost:8545" 13 | const contract = "0xD7600ae27C99988A6CD360234062b540F88ECA43" 14 | 15 | const provider = new ethers.providers.JsonRpcProvider(ethNode); 16 | 17 | const { abi } = getContractArtifacts("artifacts/contracts/Gravity.sol/Gravity.json"); 18 | 19 | const gravity = (new ethers.Contract(contract, abi, provider.getSigner()) as any) as Gravity; 20 | 21 | const events = await gravity.queryFilter({}) 22 | console.log(events) 23 | 24 | // gravity.on({}, function () { 25 | // console.log(arguments) 26 | // }); 27 | })() -------------------------------------------------------------------------------- /solidity/debugging.ts: -------------------------------------------------------------------------------- 1 | import { createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; 2 | import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; 3 | import { 4 | Query, 5 | QueryClientImpl, 6 | SignerSetTxConfirmationsResponse, 7 | } from "./gen/gravity/v1/query"; 8 | import { SignerSetTx } from "./gen/gravity/v1/gravity"; 9 | import Long from "long"; 10 | import { exit } from "process"; 11 | 12 | async function getQueryService(): Promise { 13 | const cosmosNode = "http://localhost:26657"; 14 | const tendermintClient = await Tendermint34Client.connect(cosmosNode); 15 | const queryClient = new QueryClient(tendermintClient); 16 | const rpcClient = createProtobufRpcClient(queryClient); 17 | return new QueryClientImpl(rpcClient); 18 | } 19 | 20 | async function getParams() { 21 | let queryService = await getQueryService(); 22 | const res = await queryService.Params({}); 23 | if (!res.params) { 24 | console.log("Could not retrieve params"); 25 | exit(1); 26 | } 27 | return res.params; 28 | } 29 | 30 | async function getValset(signerSetNonce: Long): Promise { 31 | let queryService = await getQueryService(); 32 | const res = await queryService.SignerSetTx({ signerSetNonce }); 33 | if (!res.signerSet) { 34 | console.log("Could not retrieve signer set", res); 35 | exit(1); 36 | } 37 | return res.signerSet; 38 | } 39 | 40 | async function getSignerSetTxConfirmations(signerSetNonce: Long): Promise { 41 | let queryService = await getQueryService(); 42 | const res = await queryService.SignerSetTxConfirmations({ signerSetNonce }); 43 | if (!res.signatures) { 44 | console.log("Could not retrieve signatures", res); 45 | exit(1); 46 | } 47 | return res; 48 | } 49 | 50 | async function getLatestValset(): Promise { 51 | let queryService = await getQueryService(); 52 | const res = await queryService.LatestSignerSetTx({}); 53 | if (!res.signerSet) { 54 | console.log("Could not retrieve signer set"); 55 | exit(1); 56 | } 57 | return res.signerSet; 58 | } 59 | 60 | async function getAllValsets() { 61 | let queryService = await getQueryService(); 62 | const res = await queryService.SignerSetTxs({}); 63 | 64 | return res; 65 | } 66 | 67 | async function getDelegateKeys() { 68 | let queryService = await getQueryService(); 69 | const res = await queryService.DelegateKeysByOrchestrator({ 70 | orchestratorAddress: "cosmos14uvqun482ydhljwtvacy5grvgh23xrmgymg0wd", 71 | }); 72 | return res; 73 | } 74 | 75 | (async function () { 76 | // console.log(await getDelegateKeys()); 77 | // console.log(JSON.stringify(await getAllValsets())); 78 | const res = await getValset(Long.fromInt(1)) 79 | console.log(JSON.stringify(res)); 80 | })(); 81 | -------------------------------------------------------------------------------- /solidity/gen/cosmos_proto/cosmos.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import Long from "long"; 3 | import _m0 from "protobufjs/minimal"; 4 | 5 | export const protobufPackage = "cosmos_proto"; 6 | 7 | if (_m0.util.Long !== Long) { 8 | _m0.util.Long = Long as any; 9 | _m0.configure(); 10 | } 11 | -------------------------------------------------------------------------------- /solidity/gen/gogoproto/gogo.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import Long from "long"; 3 | import _m0 from "protobufjs/minimal"; 4 | 5 | export const protobufPackage = "gogoproto"; 6 | 7 | if (_m0.util.Long !== Long) { 8 | _m0.util.Long = Long as any; 9 | _m0.configure(); 10 | } 11 | -------------------------------------------------------------------------------- /solidity/gen/google/api/annotations.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import Long from "long"; 3 | import _m0 from "protobufjs/minimal"; 4 | 5 | export const protobufPackage = "google.api"; 6 | 7 | if (_m0.util.Long !== Long) { 8 | _m0.util.Long = Long as any; 9 | _m0.configure(); 10 | } 11 | -------------------------------------------------------------------------------- /solidity/get-valset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/gravity-bridge/0195030967c3fef8f86da44ee89caa99564304cf/solidity/get-valset -------------------------------------------------------------------------------- /solidity/how-to-setup-hardhat.txt: -------------------------------------------------------------------------------- 1 | # Note: this has already been done for this repo. See readme.md if you are using this repo 2 | npm init 3 | npm install --save-dev ts-node typescript 4 | npm install --save-dev chai @types/node @types/mocha @types/chai 5 | npm install --save-dev hardhat-waffle ethereum-waffle chai hardhat-ethers ethers typechain typechain-target-ethers-v5 6 | npm install --save-dev hardhat 7 | npx hardhat 8 | npx hardhat test # Just to check if it works 9 | # Follow buidler typescript guide, modifying files etc 10 | # add gas reporter and typechain modules -------------------------------------------------------------------------------- /solidity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gravity-contracts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "evm": "npx hardhat node", 8 | "test": "npx hardhat test --network localhost", 9 | "typechain": "npx hardhat typechain", 10 | "compile-deployer": "npx tsc -p . ; pkg dist/contract-deployer.js --targets node10-linux-x64", 11 | "evm_fork": "npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID} --fork-block-number 11780000", 12 | "test_fork": "npx hardhat test --network localhost ./tests_mainnet_fork/*.ts" 13 | }, 14 | "author": "Jehan Tremback", 15 | "license": "None", 16 | "devDependencies": { 17 | "@commitlint/cli": "^9.1.2", 18 | "@commitlint/config-conventional": "^9.1.2", 19 | "@ethersproject/abstract-signer": "^5.0.6", 20 | "@ethersproject/bignumber": "^5.0.8", 21 | "@nomiclabs/hardhat-ethers": "^2.0.0", 22 | "@nomiclabs/hardhat-waffle": "^2.0.0", 23 | "@openzeppelin/contracts": "3.1.0", 24 | "@typechain/ethers-v5": "^5.0.0", 25 | "@types/chai": "^4.2.13", 26 | "@types/command-line-args": "^5.0.0", 27 | "@types/fs-extra": "^9.0.1", 28 | "@types/mocha": "^7.0.2", 29 | "@types/node": "^14.11.8", 30 | "@typescript-eslint/eslint-plugin": "^3.10.1", 31 | "@typescript-eslint/parser": "^3.10.1", 32 | "@uniswap/v2-periphery": "^1.1.0-beta.0", 33 | "axios": "0.21.1", 34 | "chai": "^4.2.0", 35 | "command-line-args": "^5.1.1", 36 | "commitizen": "^4.2.1", 37 | "cz-conventional-changelog": "^3.3.0", 38 | "dotenv": "^8.2.0", 39 | "eslint": "^7.11.0", 40 | "eslint-config-prettier": "^6.12.0", 41 | "ethereum-waffle": "^3.2.0", 42 | "ethers": "^5.0.24", 43 | "fs-extra": "^9.0.1", 44 | "hardhat": "^2.0.6", 45 | "hardhat-gas-reporter": "^1.0.4", 46 | "hardhat-typechain": "^0.3.4", 47 | "husky": "^4.3.0", 48 | "mocha": "^8.1.3", 49 | "nexe": "^4.0.0-beta.18", 50 | "prettier": "^2.1.2", 51 | "prettier-plugin-solidity": "^1.0.0-beta.1", 52 | "shelljs": "^0.8.4", 53 | "solc": "0.7.4", 54 | "solhint": "^3.2.1", 55 | "solhint-plugin-prettier": "^0.0.5", 56 | "solidity-coverage": "^0.7.12", 57 | "ts-generator": "^0.1.1", 58 | "ts-node": "^8.10.2", 59 | "typechain": "^4.0.1", 60 | "typescript": "^3.9.7" 61 | }, 62 | "dependencies": { 63 | "@cosmjs/stargate": "^0.25.3", 64 | "@cosmjs/tendermint-rpc": "^0.25.3", 65 | "pkg": "^4.4.9", 66 | "ts-proto": "^1.81.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /solidity/possible_optimizations.md: -------------------------------------------------------------------------------- 1 | Possible optimizations: 2 | 3 | 1. Bit packing in checkpoint hashing: 4 | 5 | - Bitpack the powers and the address into one bytes32 6 | - Might be a 50% savings, since they are now effectively in two bytes32's 7 | - This might represent a 50k gas savings per checkpoint, since each checkpoint is 100k 8 | 9 | 2. Remove "\x19Ethereum Signed Message:\n32" crap from signature verification 10 | - This would remove one hashing per validator for a total of 20k gas with 25 signatures 11 | 12 | Could be possible to save \$1.20 off of updateValset at 50gwei 13 | -------------------------------------------------------------------------------- /solidity/protocgen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eox pipefail 4 | 5 | proto_dirs=$(find ../module/proto -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 6 | mkdir -p ./gen/ 7 | for dir in $proto_dirs; do 8 | buf protoc \ 9 | -I "../module/proto" \ 10 | -I "../module/third_party/proto" \ 11 | --ts_proto_out=./gen/ \ 12 | --plugin=./node_modules/.bin/protoc-gen-ts_proto \ 13 | --ts_proto_opt="esModuleInterop=true,forceLong=long,useOptionals=true" \ 14 | $(find "${dir}" -maxdepth 2 -name '*.proto') 15 | done -------------------------------------------------------------------------------- /solidity/scripts/contract-deployer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npx ts-node \ 3 | contract-deployer.ts \ 4 | --cosmos-node="http://localhost:26657" \ 5 | --eth-node="http://localhost:8545" \ 6 | --eth-privkey="0xb1bab011e03a9862664706fc3bbaa1b16651528e5f0e7fbfcbfdd8be302a13e7" \ 7 | --contract=Gravity.json \ 8 | --test-mode=true -------------------------------------------------------------------------------- /solidity/scripts/get-valset.ts: -------------------------------------------------------------------------------- 1 | import commandLineArgs from "command-line-args"; 2 | import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios"; 3 | 4 | const args = commandLineArgs([ 5 | // the bloc to get the height at 6 | { name: "block", type: Number }, 7 | // the cosmos node that will be used to grab the validator set via RPC 8 | { name: "cosmos-node", type: String }, 9 | ]); 10 | 11 | 12 | type ValsetTypeWrapper = { 13 | type: string; 14 | value: Valset; 15 | }; 16 | type Valset = { 17 | members: Validator[]; 18 | nonce: number; 19 | }; 20 | type ABCIWrapper = { 21 | jsonrpc: string; 22 | id: string; 23 | result: ABCIResponse; 24 | }; 25 | type ABCIResponse = { 26 | response: ABCIResult 27 | }; 28 | type ABCIResult = { 29 | code: number 30 | log: string, 31 | info: string, 32 | index: string, 33 | value: string, 34 | height: string, 35 | codespace: string, 36 | }; 37 | type Validator = { 38 | power: number; 39 | ethereum_address: string; 40 | }; 41 | 42 | const decode = (str: string):string => Buffer.from(str, 'base64').toString('binary'); 43 | 44 | async function getValset(): Promise { 45 | console.log("Starting") 46 | let request_string = args["cosmos-node"] + "/abci_query" 47 | let response = await axios.get(request_string, {params: { 48 | path: "\"/custom/gravity/currentValset/\"", 49 | height: args["block"], 50 | prove: "false", 51 | }}); 52 | let valsets: ABCIWrapper = await response.data; 53 | console.log(valsets) 54 | console.log(decode(valsets.result.response.value)); 55 | let valset: ValsetTypeWrapper = JSON.parse(decode(valsets.result.response.value)) 56 | return valset.value; 57 | } 58 | 59 | 60 | async function main() { 61 | await getValset(); 62 | } 63 | 64 | main(); 65 | -------------------------------------------------------------------------------- /solidity/scripts/sample-script.js: -------------------------------------------------------------------------------- 1 | // We require the Buidler Runtime Environment explicitly here. This is optional 2 | // but useful for running the script in a standalone fashion through `node