├── .dockerignore ├── .github ├── CODEOWNERS ├── actions │ └── init │ │ └── action.yml └── workflows │ ├── ci.yml │ ├── install.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .rustfmt.toml ├── .vscode └── settings.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── cliff.toml ├── codecov.yml ├── crates ├── pop-cli │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── src │ │ ├── assets │ │ │ └── index.html │ │ ├── cli.rs │ │ ├── commands │ │ │ ├── bench │ │ │ │ ├── block.rs │ │ │ │ ├── machine.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── overhead.rs │ │ │ │ ├── pallet.rs │ │ │ │ └── storage.rs │ │ │ ├── build │ │ │ │ ├── contract.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── parachain.rs │ │ │ │ ├── runtime.rs │ │ │ │ └── spec.rs │ │ │ ├── call │ │ │ │ ├── chain.rs │ │ │ │ ├── contract.rs │ │ │ │ └── mod.rs │ │ │ ├── clean.rs │ │ │ ├── hash.rs │ │ │ ├── install │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ ├── new │ │ │ │ ├── contract.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── pallet.rs │ │ │ │ └── parachain.rs │ │ │ ├── test │ │ │ │ ├── contract.rs │ │ │ │ ├── create_snapshot.rs │ │ │ │ ├── execute_block.rs │ │ │ │ ├── fast_forward.rs │ │ │ │ ├── mod.rs │ │ │ │ └── on_runtime_upgrade.rs │ │ │ └── up │ │ │ │ ├── contract.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── network.rs │ │ │ │ └── rollup.rs │ │ ├── common │ │ │ ├── bench.rs │ │ │ ├── binary.rs │ │ │ ├── builds.rs │ │ │ ├── chain.rs │ │ │ ├── contracts.rs │ │ │ ├── helpers.rs │ │ │ ├── mod.rs │ │ │ ├── prompt.rs │ │ │ ├── runtime.rs │ │ │ ├── try_runtime.rs │ │ │ └── wallet.rs │ │ ├── deployment_api.rs │ │ ├── main.rs │ │ ├── style.rs │ │ └── wallet_integration.rs │ └── tests │ │ ├── contract.rs │ │ └── parachain.rs ├── pop-common │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── account_id.rs │ │ ├── api.rs │ │ ├── build.rs │ │ ├── errors.rs │ │ ├── git.rs │ │ ├── helpers.rs │ │ ├── lib.rs │ │ ├── manifest.rs │ │ ├── metadata.rs │ │ ├── polkadot_sdk.rs │ │ ├── signer.rs │ │ ├── sourcing │ │ ├── binary.rs │ │ └── mod.rs │ │ ├── templates │ │ ├── extractor.rs │ │ └── mod.rs │ │ └── test.rs ├── pop-contracts │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── src │ │ ├── build.rs │ │ ├── call.rs │ │ ├── errors.rs │ │ ├── lib.rs │ │ ├── new.rs │ │ ├── node.rs │ │ ├── templates.rs │ │ ├── test.rs │ │ ├── testing.rs │ │ ├── up.rs │ │ └── utils │ │ │ ├── map_account.rs │ │ │ ├── metadata.rs │ │ │ └── mod.rs │ └── tests │ │ └── files │ │ ├── testing.contract │ │ ├── testing.json │ │ ├── testing.polkavm │ │ ├── testing.wasm │ │ └── testing_wasm.contract ├── pop-parachains │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── askama.toml │ ├── src │ │ ├── accounts.rs │ │ ├── bench │ │ │ ├── binary.rs │ │ │ └── mod.rs │ │ ├── build │ │ │ ├── mod.rs │ │ │ └── runtime.rs │ │ ├── call │ │ │ ├── metadata │ │ │ │ ├── action.rs │ │ │ │ ├── mod.rs │ │ │ │ └── params.rs │ │ │ └── mod.rs │ │ ├── deployer_providers.rs │ │ ├── errors.rs │ │ ├── generator │ │ │ ├── mod.rs │ │ │ ├── pallet.rs │ │ │ └── parachain.rs │ │ ├── lib.rs │ │ ├── new_pallet.rs │ │ ├── new_pallet │ │ │ └── new_pallet_options.rs │ │ ├── new_parachain.rs │ │ ├── registry │ │ │ ├── mod.rs │ │ │ ├── pop.rs │ │ │ └── system.rs │ │ ├── relay.rs │ │ ├── templates.rs │ │ ├── traits.rs │ │ ├── try_runtime │ │ │ ├── binary.rs │ │ │ ├── mod.rs │ │ │ ├── parse.rs │ │ │ ├── shared_parameters.rs │ │ │ └── state.rs │ │ ├── up │ │ │ ├── chain_specs.rs │ │ │ ├── mod.rs │ │ │ ├── parachains.rs │ │ │ └── relay.rs │ │ └── utils │ │ │ ├── helpers.rs │ │ │ └── mod.rs │ ├── templates │ │ ├── base │ │ │ ├── chain_spec.templ │ │ │ └── network.templ │ │ └── pallet │ │ │ ├── Cargo.templ │ │ │ ├── advanced_mode │ │ │ └── src │ │ │ │ ├── benchmarking.rs.templ │ │ │ │ ├── config_preludes.rs.templ │ │ │ │ ├── lib.rs.templ │ │ │ │ ├── mock.rs.templ │ │ │ │ ├── pallet_logic.rs.templ │ │ │ │ ├── pallet_logic │ │ │ │ ├── origin.rs.templ │ │ │ │ └── try_state.rs.templ │ │ │ │ ├── tests.rs.templ │ │ │ │ ├── tests │ │ │ │ └── utils.rs.templ │ │ │ │ └── types.rs.templ │ │ │ └── simple_mode │ │ │ └── src │ │ │ ├── benchmarking.rs.templ │ │ │ ├── lib.rs.templ │ │ │ ├── mock.rs.templ │ │ │ ├── tests.rs.templ │ │ │ └── weights.rs.templ │ └── tests │ │ └── parachain.rs └── pop-telemetry │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ └── lib.rs ├── deny.toml ├── rust-toolchain.toml └── tests ├── networks ├── kusama+asset-hub.toml ├── kusama.toml ├── paseo+asset-hub.toml ├── paseo+bridge-hub.toml ├── paseo+collectives.toml ├── paseo+coretime.toml ├── paseo+people.toml ├── paseo.toml ├── polkadot+asset-hub.toml ├── polkadot+collectives.toml ├── polkadot.toml ├── pop.toml ├── template.toml ├── westend+asset-hub.toml └── westend.toml ├── runtimes ├── base_parachain.wasm ├── base_parachain_benchmark.wasm └── base_parachain_try_runtime.wasm └── snapshots └── base_parachain.snap /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | **/target/ 3 | **/*.txt 4 | /docker/ 5 | 6 | # dotfiles in the repo root 7 | /.* -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | @AlexD10S 2 | @al3mart 3 | @evilrobot-01 -------------------------------------------------------------------------------- /.github/actions/init/action.yml: -------------------------------------------------------------------------------- 1 | name: Initialize 2 | description: This action initializes a runner for use in other actions. 3 | inputs: 4 | cache-key: 5 | description: "The key to be used for the cache" 6 | git-user: 7 | required: true 8 | description: "The user name to be used for git config" 9 | 10 | runs: 11 | using: "composite" 12 | steps: 13 | - name: Setup Ubuntu dependencies 14 | shell: bash 15 | run: | 16 | sudo apt update 17 | sudo apt install -y protobuf-compiler 18 | 19 | - name: Free up space on runner 20 | shell: bash 21 | run: | 22 | sudo rm -rf "$AGENT_TOOLSDIRECTORY" /opt/ghc /usr/local/lib/android /usr/local/share/boost 23 | sudo rm -rf /usr/local/share/chromium /usr/share/dotnet /usr/share/swift 24 | sudo docker image prune -af 25 | sudo apt-get clean 26 | sudo rm -rf /var/cache/apt/archives /var/lib/apt/lists/* 27 | 28 | - name: Setup git config 29 | shell: bash 30 | run: | 31 | git config --global user.name ${{ inputs.git-user }} 32 | git config --global user.email ${{ inputs.git-user }}@users.noreply.github.com 33 | 34 | - name: Rust Cache 35 | uses: Swatinem/rust-cache@v2.7.8 36 | with: 37 | cache-on-failure: true 38 | cache-all-crates: true 39 | key: ${{ inputs.cache-key }} -------------------------------------------------------------------------------- /.github/workflows/install.yml: -------------------------------------------------------------------------------- 1 | name: pop install 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | jobs: 14 | arch: 15 | runs-on: ubuntu-latest 16 | container: archlinux:latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Install prerequisites 20 | run: pacman -Syu --needed --noconfirm cmake curl git base-devel clang protobuf 21 | - name: Install Rust 22 | run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 23 | - name: Install Pop 24 | run: | 25 | . "$HOME/.cargo/env" 26 | cargo install --locked --no-default-features --features contract,parachain --path ./crates/pop-cli 27 | - name: Run Pop install 28 | run: | 29 | . "$HOME/.cargo/env" 30 | pop install -y 31 | debian: 32 | runs-on: ubuntu-latest 33 | container: debian 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: Install prerequisites 37 | run: apt-get update && apt-get -y install build-essential cmake curl git clang protobuf-compiler 38 | - name: Install Rust 39 | run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 40 | - name: Install Pop 41 | run: | 42 | . "$HOME/.cargo/env" 43 | cargo install --locked --no-default-features --features contract,parachain --path ./crates/pop-cli 44 | - name: Run Pop Install 45 | run: | 46 | . "$HOME/.cargo/env" 47 | pop install -y 48 | macos: 49 | runs-on: macos-latest 50 | steps: 51 | - uses: actions/checkout@v4 52 | - name: Install prerequisites 53 | run: brew update && brew install cmake openssl protobuf 54 | - name: Install Rust 55 | run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 56 | - name: Install Pop 57 | run: | 58 | . "$HOME/.cargo/env" 59 | cargo install --locked --no-default-features --features contract,parachain --path ./crates/pop-cli 60 | - name: Run Pop Install 61 | run: | 62 | . "$HOME/.cargo/env" 63 | pop install -y 64 | redhat: 65 | runs-on: ubuntu-latest 66 | container: redhat/ubi8 67 | steps: 68 | - uses: actions/checkout@v4 69 | - name: Install prerequisites 70 | run: yum update -y && yum install -y perl-IPC-Cmd clang curl git make cmake protobuf-compiler gcc pkg-config openssl-devel 71 | - name: Install Rust 72 | run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 73 | - name: Install Pop 74 | run: | 75 | . "$HOME/.cargo/env" 76 | cargo install --locked --no-default-features --features contract,parachain --path ./crates/pop-cli 77 | - name: Run Pop install 78 | run: | 79 | . "$HOME/.cargo/env" 80 | pop install -y 81 | ubuntu: 82 | runs-on: ubuntu-latest 83 | container: ubuntu 84 | steps: 85 | - uses: actions/checkout@v4 86 | - name: Install prerequisites 87 | run: apt-get update && apt-get -y install build-essential cmake curl git clang protobuf-compiler 88 | - name: Install Rust 89 | run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 90 | - name: Install Pop 91 | run: | 92 | . "$HOME/.cargo/env" 93 | cargo install --locked --no-default-features --features contract,parachain --path ./crates/pop-cli 94 | - name: Run Pop install 95 | run: | 96 | . "$HOME/.cargo/env" 97 | pop install -y 98 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: pop-cli release 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | workflow_dispatch: 7 | inputs: 8 | ref: 9 | description: ref to build binary from 10 | required: false 11 | 12 | jobs: 13 | build-node: 14 | runs-on: ${{ matrix.platform.os }} 15 | permissions: 16 | contents: write 17 | strategy: 18 | matrix: 19 | platform: 20 | # Linux 21 | - os: ubuntu-22.04 22 | target: aarch64-unknown-linux-gnu 23 | - os: ubuntu-22.04 24 | target: x86_64-unknown-linux-gnu 25 | # macOS 26 | - os: macos-14 27 | target: aarch64-apple-darwin 28 | - os: macos-14 29 | target: x86_64-apple-darwin 30 | env: 31 | RUSTFLAGS: "${{ matrix.platform.cpu != '' && format('-C target-cpu={0}', matrix.platform.cpu) || '' }} ${{ matrix.platform.target == 'aarch64-unknown-linux-gnu' && '-C linker=aarch64-linux-gnu-gcc' || '' }}" 32 | path: "target/${{ matrix.platform.target }}/production" 33 | package: "pop-${{ matrix.platform.target }}${{ matrix.platform.cpu != '' && format('-{0}', matrix.platform.cpu) || '' }}.tar.gz" 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v4 37 | with: 38 | fetch-depth: 0 39 | ref: ${{ github.event.inputs.ref }} 40 | 41 | - name: Install packages (Linux) 42 | if: contains(matrix.platform.target, 'linux') 43 | run: | 44 | sudo apt-get install -y protobuf-compiler ${{ contains(matrix.platform.target, 'aarch64') && 'crossbuild-essential-arm64' || '' }} 45 | protoc --version 46 | 47 | - name: Install packages (macOS) 48 | if: contains(matrix.platform.target, 'apple') 49 | run: | 50 | brew install protobuf 51 | protoc --version 52 | 53 | - name: Add target 54 | run: rustup target add ${{ matrix.platform.target }} 55 | 56 | - name: Build pop-cli 57 | run: cargo build --profile=production -p pop-cli --target ${{ matrix.platform.target }} 58 | 59 | - name: Package binary (Linux) 60 | if: contains(matrix.platform.target, 'linux') 61 | run: | 62 | cd ${{ env.path }} 63 | sha256sum pop > pop.sha256 64 | tar -czf ${{ env.package }} pop pop.sha256 65 | 66 | - name: Package binary (macOS) 67 | if: contains(matrix.platform.target, 'apple') 68 | run: | 69 | cd ${{ env.path }} 70 | shasum -a 256 pop > pop.sha256 71 | tar -czf ${{ env.package }} pop pop.sha256 72 | 73 | - name: Upload binary 74 | uses: actions/upload-artifact@v4 75 | with: 76 | name: binaries 77 | path: ${{ env.path }}/${{ env.package }} 78 | 79 | - name: Add binary to release 80 | if: github.event_name == 'release' 81 | uses: softprops/action-gh-release@v1 82 | with: 83 | files: | 84 | ${{ env.path }}/${{ env.package }} 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | **/node_modules/ 3 | /src/x.rs 4 | 5 | debug 6 | **/debug/ 7 | 8 | .DS_Store 9 | 10 | # IDEs 11 | .idea 12 | .vscode 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "templates/base-parachain"] 2 | path = templates/base-parachain 3 | url = git@github.com:r0gue-io/base-parachain.git 4 | 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Basic 2 | edition = "2021" 3 | hard_tabs = true 4 | max_width = 100 5 | use_small_heuristics = "Max" 6 | # Imports 7 | imports_granularity = "Crate" 8 | reorder_imports = true 9 | # Consistency 10 | newline_style = "Unix" 11 | # Misc 12 | chain_width = 80 13 | spaces_around_ranges = false 14 | binop_separator = "Back" 15 | reorder_impl_items = false 16 | match_arm_leading_pipes = "Preserve" 17 | match_arm_blocks = false 18 | match_block_trailing_comma = true 19 | trailing_comma = "Vertical" 20 | trailing_semicolon = false 21 | use_field_init_shorthand = true 22 | # Format comments 23 | comment_width = 100 24 | wrap_comments = true -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.showUnlinkedFileNotification": false 3 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [profile.release] 2 | panic = "unwind" 3 | opt-level = 3 4 | debug = false 5 | 6 | [profile.production] 7 | inherits = "release" 8 | lto = true 9 | codegen-units = 1 10 | 11 | [workspace] 12 | resolver = "2" 13 | members = ["crates/*"] 14 | 15 | [workspace.package] 16 | edition = "2021" 17 | documentation = "https://learn.onpop.io/" 18 | license = "GPL-3.0" 19 | repository = "https://github.com/r0gue-io/pop-cli" 20 | rust-version = "1.81.0" 21 | version = "0.8.1" 22 | 23 | [workspace.dependencies] 24 | anyhow = { version = "1.0", default-features = false } 25 | assert_cmd = { version = "2.0.14", default-features = false } 26 | bytes = { version = "1.10.1", default-features = false } 27 | cargo_toml = { version = "0.20.3", default-features = false } 28 | dirs = { version = "5.0", default-features = false } 29 | duct = { version = "0.13", default-features = false } 30 | env_logger = { version = "0.11.7", default-features = false } 31 | flate2 = "1.0.30" 32 | git2 = { version = "0.18", default-features = true, features = ["vendored-openssl"] } 33 | glob = { version = "0.3.1", default-features = false } 34 | log = { version = "0.4.20", default-features = false } 35 | mockito = { version = "1.4.0", default-features = false } 36 | tar = { version = "0.4.40", default-features = false } 37 | tempfile = { version = "3.10", default-features = false } 38 | thiserror = { version = "1.0.58", default-features = false } 39 | tokio-test = { version = "0.4.4", default-features = false } 40 | toml = { version = "0.5.0", default-features = false } 41 | tracing-subscriber = { version = "0.3.19", default-features = false } 42 | 43 | # networking 44 | reqwest = { version = "0.12", default-features = false, features = ["default-tls", "json", "multipart", "stream"] } 45 | tokio = { version = "1.0", default-features = false, features = ["macros", "process", "rt-multi-thread"] } 46 | url = "2.5.4" 47 | 48 | # contracts 49 | subxt-signer = { version = "0.38.0", default-features = false, features = ["subxt", "sr25519"] } 50 | subxt = { version = "0.38.0", default-features = false } 51 | ink_env = { version = "5.0.0", default-features = false } 52 | ink_env_v6 = { version = "6.0.0-alpha", package = "ink_env", default-features = false } 53 | sp-core = { version = "32.0.0", default-features = false } 54 | sp-core_inkv6 = { version = "36.1.0", package = "sp-core", default-features = false } 55 | sp-weights = { version = "31.0.0", default-features = false } 56 | scale = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } 57 | scale-info = { version = "2.11.4", default-features = false, features = ["derive"] } 58 | scale-value = { version = "0.17.0", default-features = false, features = ["from-string", "parser-ss58"] } 59 | contract-build = { version = "5.0.2", default-features = false } 60 | contract-build_inkv6 = { version = "6.0.0-alpha", package = "contract-build", default-features = false } 61 | contract-extrinsics = { version = "5.0.2", default-features = false } 62 | contract-extrinsics_inkv6 = { version = "6.0.0-alpha", package = "contract-extrinsics", default-features = false } 63 | contract-transcode = { version = "5.0.2", default-features = false } 64 | contract-transcode_inkv6 = { version = "6.0.0-alpha", package = "contract-transcode", default-features = false } 65 | heck = { version = "0.5.0", default-features = false } 66 | 67 | # parachains 68 | askama = { version = "0.12", default-features = false, features = ["config"] } 69 | regex = { version = "1.10", default-features = false } 70 | walkdir = { version = "2.5", default-features = false } 71 | indexmap = { version = "2.2", default-features = false } 72 | toml_edit = { version = "0.22", features = ["serde"] } 73 | symlink = { version = "0.1", default-features = false } 74 | serde_json = { version = "1.0", default-features = false, features = ["preserve_order"] } 75 | serde = { version = "1.0", default-features = false, features = ["derive"] } 76 | srtool-lib = { version = "0.13.2", default-features = false } 77 | zombienet-configuration = { version = "0.3.1", default-features = false } 78 | zombienet-sdk = { version = "0.3.1", default-features = false } 79 | git2_credentials = "0.13.0" 80 | 81 | # benchmarking 82 | cumulus-primitives-proof-size-hostfunction = "0.12.0" 83 | frame-benchmarking-cli = { version = "47.0.0", default-features = false } 84 | sc-chain-spec = { version = "42.0.0", default-features = false } 85 | sp-runtime = { version = "41.1.0", default-features = false } 86 | sp-statement-store = "20.1.0" 87 | 88 | # try-runtime 89 | frame-try-runtime = "0.45.0" 90 | sc-cli = { version = "0.51.0", default-features = false } 91 | sp-version = { version = "38.0.0", default-features = false } 92 | 93 | # pop-cli 94 | clap = { version = "4.5", default-features = false, features = ["derive", "string"] } 95 | cliclack = { version = "0.3.1", default-features = false } 96 | console = { version = "0.15", default-features = false } 97 | os_info = { version = "3", default-features = false } 98 | strum = { version = "0.26", default-features = false } 99 | strum_macros = { version = "0.26", default-features = false } 100 | 101 | # wallet-integration 102 | axum = { version = "0.7.9", default-features = false, features = ["http1", "json", "tokio"] } 103 | open = { version = "5.3.1", default-features = false } 104 | tower-http = { version = "0.6.2", default-features = false } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust as builder 2 | RUN apt-get update && apt-get -y install cmake \ 3 | && apt-get install -y clang \ 4 | && apt-get install --no-install-recommends --assume-yes protobuf-compiler 5 | WORKDIR /pop 6 | COPY . /pop 7 | RUN rustup show active-toolchain || rustup toolchain install 8 | RUN cargo build --release 9 | 10 | # Build image, preinstalling all dependencies for general Polkadot development 11 | FROM rust:slim 12 | COPY --from=builder /pop/target/release/pop /usr/bin/pop 13 | RUN apt-get update && pop install -y && apt-get clean 14 | CMD ["/usr/bin/pop"] 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pop CLI 2 | 3 | 4 | 5 |
6 | 7 | [![Twitter URL](https://img.shields.io/twitter/follow/Pop?style=social)](https://x.com/onpopio/) 8 | [![Twitter URL](https://img.shields.io/twitter/follow/R0GUE?style=social)](https://twitter.com/gor0gue) 9 | [![Telegram](https://img.shields.io/badge/Telegram-gray?logo=telegram)](https://t.me/onpopio) 10 | 11 | 12 | 13 | > An all-in-one tool for Polkadot development. 14 | 15 |
16 | 17 | ## Installation 18 | 19 | You can install Pop CLI from [crates.io](https://crates.io/crates/pop-cli): 20 | 21 | ```shell 22 | cargo install --force --locked pop-cli 23 | ``` 24 | 25 | > :information_source: Pop CLI requires Rust 1.81 or later. 26 | 27 | You can also install Pop CLI using the [Pop CLI GitHub repo](https://github.com/r0gue-io/pop-cli): 28 | 29 | ```shell 30 | cargo install --locked --git https://github.com/r0gue-io/pop-cli 31 | ``` 32 | 33 | > :information_source: For detailed instructions on how to install Pop CLI, please refer to our 34 | > documentation: 35 | 36 | ### Telemetry 37 | 38 | Pop CLI collects anonymous usage metrics to help us understand how the tool is being used and how we can improve it. 39 | We do not collect any personal information. If you wish to disable telemetry 40 | or read more about our telemetry practices please see 41 | our [telemetry](crates/pop-telemetry/README.md) documentation. 42 | 43 | ## Documentation 44 | 45 | On the [Pop Docs website](https://learn.onpop.io) you will find: 46 | 47 | * 👉 [Get Started with Pop CLI](https://learn.onpop.io/v/cli) 48 | 49 | ## Building Pop CLI locally 50 | 51 | Build the tool locally with all the features: 52 | 53 | ```sh 54 | cargo build --all-features 55 | ``` 56 | 57 | Build the tool only for Parachain functionality: 58 | 59 | ```sh 60 | cargo build --no-default-features --features parachain 61 | ``` 62 | 63 | Build the tool only for Smart Contracts functionality: 64 | 65 | ```sh 66 | cargo build --no-default-features --features contract 67 | ``` 68 | 69 | ## Testing Pop CLI 70 | 71 | To test the tool locally. Due to the time it can take to build a Parachain or a Smart Contract, some tests have been 72 | separated from the normal testing flow into integration tests. 73 | 74 | Run the unit tests only: 75 | 76 | ```sh 77 | cargo test --lib 78 | ``` 79 | 80 | To run the integration tests relating to Smart Contracts: 81 | 82 | ```sh 83 | cargo test --test contract 84 | ``` 85 | 86 | To run the integration tests relating to Parachains: 87 | 88 | ```sh 89 | cargo test --test parachain 90 | ``` 91 | 92 | Run all tests (unit + integration): 93 | 94 | ```sh 95 | cargo test 96 | ``` 97 | 98 | > Running tests may result in rate limits being exhausted due to the reliance on the GitHub REST API for determining 99 | > releases. As 100 | > per , a 101 | > personal access token can be used via the `GITHUB_TOKEN` environment variable. 102 | 103 | ## Acknowledgements 104 | 105 | Pop CLI would not be possible without these awesome crates! 106 | 107 | - Local network deployment powered by [zombienet-sdk](https://github.com/paritytech/zombienet-sdk) 108 | - [cargo contract](https://github.com/use-ink/cargo-contract) a setup and deployment tool for developing Wasm based 109 | Smart Contracts via ink! 110 | - Build deterministic runtimes powered by [srtool-cli](https://github.com/chevdor/srtool-cli) 111 | 112 | ## License 113 | 114 | The entire code within this repository is licensed under the [GPLv3](./LICENSE). 115 | 116 | Please [contact us](https://r0gue.io/contact) if you have questions about the licensing of our products. 117 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # git-cliff ~ default configuration file 2 | # https://git-cliff.org/docs/configuration 3 | # 4 | # Lines starting with "#" are comments. 5 | # Configuration options are organized into tables and keys. 6 | # See documentation for more information on available options. 7 | 8 | [changelog] 9 | # changelog header 10 | header = """ 11 | # Changelog\n 12 | All notable changes to this project will be documented in this file.\n 13 | """ 14 | # template for the changelog body 15 | # https://keats.github.io/tera/docs/#introduction 16 | body = """ 17 | {% if version %}\ 18 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 19 | {% else %}\ 20 | ## [unreleased] 21 | {% endif %}\ 22 | {% for group, commits in commits | group_by(attribute="group") %} 23 | ### {{ group | striptags | trim | upper_first }} 24 | {% for commit in commits %} 25 | - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ 26 | {% if commit.breaking %}[**breaking**] {% endif %}\ 27 | {{ commit.message | upper_first }}\ 28 | {% endfor %} 29 | {% endfor %}\n 30 | """ 31 | # template for the changelog footer 32 | footer = """ 33 | 34 | """ 35 | # remove the leading and trailing s 36 | trim = true 37 | # postprocessors 38 | postprocessors = [ 39 | # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL 40 | ] 41 | 42 | [git] 43 | # parse the commits based on https://www.conventionalcommits.org 44 | conventional_commits = true 45 | # filter out the commits that are not conventional 46 | filter_unconventional = true 47 | # process each line of a commit as an individual commit 48 | split_commits = false 49 | # regex for preprocessing the commit messages 50 | commit_preprocessors = [ 51 | # Replace issue numbers 52 | #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, 53 | # Check spelling of the commit with https://github.com/crate-ci/typos 54 | # If the spelling is incorrect, it will be automatically fixed. 55 | #{ pattern = '.*', replace_command = 'typos --write-changes -' }, 56 | ] 57 | # regex for parsing and grouping commits 58 | commit_parsers = [ 59 | { message = "^feat", group = "🚀 Features" }, 60 | { message = "^fix", group = "🐛 Fixes" }, 61 | { message = "^doc", group = "📚 Documentation" }, 62 | { message = "^perf", group = "⚡ Performance" }, 63 | { message = "^refactor", group = "🚜 Refactor" }, 64 | { message = "^style", group = "🎨 Styling" }, 65 | { message = "^test", group = "🧪 Testing" }, 66 | { message = "^chore\\(release\\): prepare for", skip = true }, 67 | { message = "^chore\\(deps.*\\)", skip = true }, 68 | { message = "^chore\\(pr\\)", skip = true }, 69 | { message = "^chore\\(pull\\)", skip = true }, 70 | { message = "^chore|^ci", group = "⚙️ Miscellaneous Tasks" }, 71 | { body = ".*security", group = "🛡️ Security" }, 72 | { message = "^revert", group = "◀️ Revert" }, 73 | ] 74 | # protect breaking changes from being skipped due to matching a skipping commit_parser 75 | protect_breaking_commits = false 76 | # filter out the commits that are not matched by commit parsers 77 | filter_commits = false 78 | # regex for matching git tags 79 | # tag_pattern = "v[0-9].*" 80 | # regex for skipping tags 81 | # skip_tags = "" 82 | # regex for ignoring tags 83 | # ignore_tags = "" 84 | # sort the tags topologically 85 | topo_order = false 86 | # sort the commits inside sections by oldest/newest order 87 | sort_commits = "oldest" 88 | # limit the number of commits included in the changelog. 89 | # limit_commits = 42 90 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # https://docs.codecov.com/docs/common-recipe-list 2 | coverage: 3 | status: 4 | project: 5 | default: 6 | target: auto 7 | threshold: 0% 8 | comment: 9 | layout: " diff, flags, files" 10 | behavior: default 11 | require_changes: false 12 | require_base: false 13 | require_head: true 14 | hide_project_coverage: false 15 | -------------------------------------------------------------------------------- /crates/pop-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pop-cli" 3 | description = "An all-in-one tool for Polkadot development." 4 | version.workspace = true 5 | edition.workspace = true 6 | documentation.workspace = true 7 | license.workspace = true 8 | readme = "README.md" 9 | repository.workspace = true 10 | 11 | [[bin]] 12 | name = "pop" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | anyhow.workspace = true 17 | clap.workspace = true 18 | cliclack.workspace = true 19 | console.workspace = true 20 | dirs.workspace = true 21 | duct.workspace = true 22 | env_logger.workspace = true 23 | os_info.workspace = true 24 | reqwest.workspace = true 25 | serde = { workspace = true, features = ["derive"] } 26 | serde_json.workspace = true 27 | strum.workspace = true 28 | strum_macros.workspace = true 29 | tempfile.workspace = true 30 | tokio.workspace = true 31 | toml.workspace = true 32 | url.workspace = true 33 | 34 | # contracts 35 | pop-contracts = { path = "../pop-contracts", version = "0.8.1", default-features = false, optional = true } 36 | sp-core = { workspace = true, optional = true } 37 | sp-weights = { workspace = true, optional = true } 38 | 39 | # parachains 40 | pop-parachains = { path = "../pop-parachains", version = "0.8.1", optional = true } 41 | git2 = { workspace = true, optional = true } 42 | regex = { workspace = true, optional = true } 43 | tracing-subscriber = { workspace = true, optional = true } 44 | 45 | # telemetry 46 | pop-telemetry = { path = "../pop-telemetry", version = "0.8.1", optional = true } 47 | 48 | # common 49 | pop-common = { path = "../pop-common", version = "0.8.1" } 50 | 51 | # wallet-integration 52 | axum = { workspace = true, optional = true } 53 | open = { workspace = true, optional = true } 54 | tower-http = { workspace = true, features = ["fs", "cors"], optional = true } 55 | 56 | [dev-dependencies] 57 | assert_cmd.workspace = true 58 | contract-extrinsics.workspace = true 59 | mockito.workspace = true 60 | subxt.workspace = true 61 | subxt-signer.workspace = true 62 | 63 | [features] 64 | default = ["parachain", "telemetry", "wasm-contracts"] 65 | contract = ["wasm-contracts"] 66 | contracts = ["polkavm-contracts"] 67 | experimental = ["hashing"] 68 | hashing = ["dep:sp-core"] 69 | parachain = ["dep:pop-parachains", "dep:git2", "dep:regex", "dep:sp-core", "dep:tracing-subscriber", "wallet-integration"] 70 | v6 = [] 71 | polkavm-contracts = ["pop-contracts/v6", "dep:pop-contracts", "dep:sp-core", "dep:sp-weights", "wallet-integration"] 72 | telemetry = ["dep:pop-telemetry"] 73 | v5 = [] 74 | wasm-contracts = ["pop-contracts/v5", "dep:pop-contracts", "dep:sp-core", "dep:sp-weights", "wallet-integration", "v5"] 75 | wallet-integration = ["dep:axum", "dep:open", "dep:tower-http"] -------------------------------------------------------------------------------- /crates/pop-cli/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /crates/pop-cli/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /crates/pop-cli/src/commands/bench/block.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{ 4 | cli::{self}, 5 | common::{ 6 | builds::{ensure_node_binary_exists, guide_user_to_select_profile}, 7 | prompt::display_message, 8 | runtime::Feature, 9 | }, 10 | }; 11 | use clap::Args; 12 | use pop_common::Profile; 13 | use pop_parachains::{bench::BlockCmd, generate_binary_benchmarks, BenchmarkingCliCommand}; 14 | use std::{ 15 | env::current_dir, 16 | path::{Path, PathBuf}, 17 | }; 18 | 19 | const EXCLUDED_ARGS: [&str; 1] = ["--profile"]; 20 | 21 | #[derive(Args)] 22 | pub(crate) struct BenchmarkBlock { 23 | /// Command to benchmark the execution time of historic blocks. 24 | #[clap(flatten)] 25 | pub command: BlockCmd, 26 | /// Build profile. 27 | #[clap(long, value_enum)] 28 | pub(crate) profile: Option, 29 | } 30 | 31 | impl BenchmarkBlock { 32 | pub(crate) fn execute(&mut self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<()> { 33 | self.benchmark(cli, ¤t_dir().unwrap_or(PathBuf::from("./"))) 34 | } 35 | 36 | fn benchmark( 37 | &mut self, 38 | cli: &mut impl cli::traits::Cli, 39 | target_path: &Path, 40 | ) -> anyhow::Result<()> { 41 | cli.intro("Benchmarking the execution time of historic blocks")?; 42 | 43 | if self.profile.is_none() { 44 | self.profile = Some(guide_user_to_select_profile(cli)?); 45 | }; 46 | let binary_path = ensure_node_binary_exists( 47 | cli, 48 | target_path, 49 | self.profile.as_ref().ok_or_else(|| anyhow::anyhow!("No profile provided"))?, 50 | vec![Feature::Benchmark.as_ref()], 51 | )?; 52 | 53 | cli.warning("NOTE: this may take some time...")?; 54 | 55 | let result = generate_binary_benchmarks( 56 | &binary_path, 57 | BenchmarkingCliCommand::Block, 58 | |args| args, 59 | &EXCLUDED_ARGS, 60 | ); 61 | 62 | // Display the benchmarking command. 63 | cliclack::log::remark("\n")?; 64 | cli.info(self.display())?; 65 | if let Err(e) = result { 66 | return display_message(&e.to_string(), false, cli); 67 | } 68 | display_message("Benchmark completed successfully!", true, cli)?; 69 | Ok(()) 70 | } 71 | 72 | fn display(&self) -> String { 73 | let mut args = vec!["pop bench block".to_string()]; 74 | let mut arguments: Vec = std::env::args().skip(3).collect(); 75 | if !argument_exists(&arguments, "--profile") { 76 | if let Some(ref profile) = self.profile { 77 | arguments.push(format!("--profile={}", profile)); 78 | } 79 | } 80 | args.extend(arguments); 81 | args.join(" ") 82 | } 83 | } 84 | 85 | fn argument_exists(args: &[String], arg: &str) -> bool { 86 | args.iter().any(|a| a.contains(arg)) 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use crate::cli::MockCli; 92 | use clap::Parser; 93 | use duct::cmd; 94 | use pop_common::Profile; 95 | use std::fs::{self, File}; 96 | use tempfile::tempdir; 97 | 98 | use super::*; 99 | 100 | #[test] 101 | fn benchmark_block_works() -> anyhow::Result<()> { 102 | let name = "node"; 103 | let temp_dir = tempdir()?; 104 | cmd("cargo", ["new", name, "--bin"]).dir(temp_dir.path()).run()?; 105 | let target_path = Profile::Debug.target_directory(temp_dir.path()); 106 | 107 | fs::create_dir(&temp_dir.path().join("target"))?; 108 | fs::create_dir(&target_path)?; 109 | File::create(target_path.join("node"))?; 110 | 111 | // With `profile` provided. 112 | let mut cli = MockCli::new() 113 | .expect_intro("Benchmarking the execution time of historic blocks") 114 | .expect_warning("NOTE: this may take some time...") 115 | .expect_info("pop bench block --profile=debug") 116 | .expect_outro_cancel( 117 | // As we only mock the node to test the interactive flow, the returned error is 118 | // expected. 119 | "Failed to run benchmarking: Permission denied (os error 13)", 120 | ); 121 | BenchmarkBlock { 122 | command: BlockCmd::try_parse_from(vec!["", "--from=0", "--to=1"])?, 123 | profile: Some(Profile::Debug), 124 | } 125 | .benchmark(&mut cli, temp_dir.path())?; 126 | cli.verify()?; 127 | 128 | let mut cli = MockCli::new() 129 | .expect_intro("Benchmarking the execution time of historic blocks") 130 | .expect_select( 131 | "Choose the build profile of the binary that should be used: ", 132 | Some(true), 133 | true, 134 | Some(Profile::get_variants()), 135 | 0, 136 | None, 137 | ) 138 | .expect_warning("NOTE: this may take some time...") 139 | .expect_info("pop bench block --profile=debug") 140 | .expect_outro_cancel( 141 | // As we only mock the node to test the interactive flow, the returned error is 142 | // expected. 143 | "Failed to run benchmarking: Permission denied (os error 13)", 144 | ); 145 | BenchmarkBlock { 146 | command: BlockCmd::try_parse_from(vec!["", "--from=0", "--to=1"])?, 147 | profile: None, 148 | } 149 | .benchmark(&mut cli, temp_dir.path())?; 150 | cli.verify() 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /crates/pop-cli/src/commands/bench/machine.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{ 4 | cli::{self}, 5 | common::{ 6 | builds::{ensure_node_binary_exists, guide_user_to_select_profile}, 7 | prompt::display_message, 8 | runtime::Feature::Benchmark, 9 | }, 10 | }; 11 | use clap::Args; 12 | use pop_common::Profile; 13 | use pop_parachains::{bench::MachineCmd, generate_binary_benchmarks, BenchmarkingCliCommand}; 14 | use std::{ 15 | env::current_dir, 16 | path::{Path, PathBuf}, 17 | }; 18 | 19 | const EXCLUDED_ARGS: [&str; 1] = ["--profile"]; 20 | 21 | #[derive(Args)] 22 | pub(crate) struct BenchmarkMachine { 23 | /// Command to benchmark the hardware. 24 | #[clap(flatten)] 25 | pub command: MachineCmd, 26 | /// Build profile. 27 | #[clap(long, value_enum)] 28 | pub(crate) profile: Option, 29 | } 30 | 31 | impl BenchmarkMachine { 32 | pub(crate) fn execute(&mut self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<()> { 33 | self.benchmark(cli, ¤t_dir().unwrap_or(PathBuf::from("./"))) 34 | } 35 | 36 | fn benchmark( 37 | &mut self, 38 | cli: &mut impl cli::traits::Cli, 39 | target_path: &Path, 40 | ) -> anyhow::Result<()> { 41 | cli.intro("Benchmarking the hardware")?; 42 | 43 | if self.profile.is_none() { 44 | self.profile = Some(guide_user_to_select_profile(cli)?); 45 | }; 46 | let binary_path = ensure_node_binary_exists( 47 | cli, 48 | target_path, 49 | self.profile.as_ref().ok_or_else(|| anyhow::anyhow!("No profile provided"))?, 50 | vec![Benchmark.as_ref()], 51 | )?; 52 | 53 | cli.warning("NOTE: this may take some time...")?; 54 | cli.info("Benchmarking your hardware performance...")?; 55 | 56 | let result = generate_binary_benchmarks( 57 | &binary_path, 58 | BenchmarkingCliCommand::Machine, 59 | |args| args, 60 | &EXCLUDED_ARGS, 61 | ); 62 | 63 | // Display the benchmarking command. 64 | cliclack::log::remark("\n")?; 65 | cli.info(self.display())?; 66 | if let Err(e) = result { 67 | return display_message(&e.to_string(), false, cli); 68 | } 69 | display_message("Benchmark completed successfully!", true, cli)?; 70 | Ok(()) 71 | } 72 | 73 | fn display(&self) -> String { 74 | let mut args = vec!["pop bench machine".to_string()]; 75 | let mut arguments: Vec = std::env::args().skip(3).collect(); 76 | if !argument_exists(&arguments, "--profile") { 77 | if let Some(ref profile) = self.profile { 78 | arguments.push(format!("--profile={}", profile)); 79 | } 80 | } 81 | args.extend(arguments); 82 | args.join(" ") 83 | } 84 | } 85 | 86 | fn argument_exists(args: &[String], arg: &str) -> bool { 87 | args.iter().any(|a| a.contains(arg)) 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use crate::cli::MockCli; 93 | use clap::Parser; 94 | use duct::cmd; 95 | use pop_common::Profile; 96 | use std::fs::{self, File}; 97 | use tempfile::tempdir; 98 | 99 | use super::*; 100 | 101 | #[test] 102 | fn benchmark_machine_works() -> anyhow::Result<()> { 103 | let name = "node"; 104 | let temp_dir = tempdir()?; 105 | cmd("cargo", ["new", name, "--bin"]).dir(temp_dir.path()).run()?; 106 | let target_path = Profile::Debug.target_directory(temp_dir.path()); 107 | 108 | fs::create_dir(&temp_dir.path().join("target"))?; 109 | fs::create_dir(&target_path)?; 110 | File::create(target_path.join("node"))?; 111 | 112 | // With `profile` provided. 113 | let mut cli = MockCli::new() 114 | .expect_intro("Benchmarking the hardware") 115 | .expect_warning("NOTE: this may take some time...") 116 | .expect_info("Benchmarking your hardware performance...") 117 | .expect_info("pop bench machine --profile=debug") 118 | .expect_outro_cancel( 119 | // As we only mock the node to test the interactive flow, the returned error is 120 | // expected. 121 | "Failed to run benchmarking: Permission denied (os error 13)", 122 | ); 123 | BenchmarkMachine { 124 | command: MachineCmd::try_parse_from(vec!["", "--allow-fail"])?, 125 | profile: Some(Profile::Debug), 126 | } 127 | .benchmark(&mut cli, temp_dir.path())?; 128 | cli.verify()?; 129 | 130 | let mut cli = MockCli::new() 131 | .expect_intro("Benchmarking the hardware") 132 | .expect_select( 133 | "Choose the build profile of the binary that should be used: ", 134 | Some(true), 135 | true, 136 | Some(Profile::get_variants()), 137 | 0, 138 | None, 139 | ) 140 | .expect_warning("NOTE: this may take some time...") 141 | .expect_info("Benchmarking your hardware performance...") 142 | .expect_info("pop bench machine --profile=debug") 143 | .expect_outro_cancel( 144 | // As we only mock the node to test the interactive flow, the returned error is 145 | // expected. 146 | "Failed to run benchmarking: Permission denied (os error 13)", 147 | ); 148 | BenchmarkMachine { 149 | command: MachineCmd::try_parse_from(vec!["", "--allow-fail"])?, 150 | profile: None, 151 | } 152 | .benchmark(&mut cli, temp_dir.path())?; 153 | cli.verify() 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /crates/pop-cli/src/commands/bench/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::cli::{self}; 4 | use block::BenchmarkBlock; 5 | use clap::{Args, Subcommand}; 6 | use machine::BenchmarkMachine; 7 | use overhead::BenchmarkOverhead; 8 | use pallet::BenchmarkPallet; 9 | use std::fmt::{Display, Formatter, Result}; 10 | use storage::BenchmarkStorage; 11 | use tracing_subscriber::EnvFilter; 12 | 13 | mod block; 14 | mod machine; 15 | mod overhead; 16 | mod pallet; 17 | mod storage; 18 | 19 | /// Arguments for benchmarking a project. 20 | #[derive(Args)] 21 | pub struct BenchmarkArgs { 22 | #[command(subcommand)] 23 | pub command: Command, 24 | } 25 | 26 | /// Benchmark a pallet or a parachain. 27 | #[derive(Subcommand)] 28 | pub enum Command { 29 | /// Benchmark the execution time of historic blocks. 30 | #[clap(alias = "b")] 31 | Block(BenchmarkBlock), 32 | /// Benchmark the machine performance. 33 | #[clap(alias = "m")] 34 | Machine(BenchmarkMachine), 35 | /// Benchmark the execution overhead per-block and per-extrinsic. 36 | #[clap(alias = "o")] 37 | Overhead(BenchmarkOverhead), 38 | /// Benchmark the extrinsic weight of pallets. 39 | #[clap(alias = "p")] 40 | Pallet(BenchmarkPallet), 41 | /// Benchmark the storage speed of a chain snapshot. 42 | #[clap(alias = "s")] 43 | Storage(BenchmarkStorage), 44 | } 45 | 46 | impl Command { 47 | /// Executes the command. 48 | pub(crate) async fn execute(args: BenchmarkArgs) -> anyhow::Result<()> { 49 | // Disable these log targets because they are spammy. 50 | let unwanted_targets = [ 51 | "cranelift_codegen", 52 | "wasm_cranelift", 53 | "wasmtime_jit", 54 | "wasmtime_cranelift", 55 | "wasm_jit", 56 | ]; 57 | 58 | let env_filter = unwanted_targets.iter().fold( 59 | EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), 60 | |filter, &target| filter.add_directive(format!("{target}=off").parse().unwrap()), 61 | ); 62 | tracing_subscriber::fmt() 63 | .with_env_filter(env_filter) 64 | .with_writer(std::io::stderr) 65 | .init(); 66 | let mut cli = cli::Cli; 67 | match args.command { 68 | Command::Block(mut cmd) => cmd.execute(&mut cli), 69 | Command::Machine(mut cmd) => cmd.execute(&mut cli), 70 | Command::Overhead(mut cmd) => cmd.execute(&mut cli).await, 71 | Command::Pallet(mut cmd) => cmd.execute(&mut cli).await, 72 | Command::Storage(mut cmd) => cmd.execute(&mut cli), 73 | } 74 | } 75 | } 76 | 77 | impl Display for Command { 78 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 79 | use Command::*; 80 | match self { 81 | Block(_) => write!(f, "block"), 82 | Machine(_) => write!(f, "machine"), 83 | Overhead(_) => write!(f, "overhead"), 84 | Pallet(_) => write!(f, "pallet"), 85 | Storage(_) => write!(f, "storage"), 86 | } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | 94 | // Others can not be tested yet due to private external types. 95 | #[test] 96 | fn command_display_works() { 97 | assert_eq!(Command::Pallet(Default::default()).to_string(), "pallet"); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /crates/pop-cli/src/commands/build/contract.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::cli; 4 | use pop_contracts::{build_smart_contract, Verbosity}; 5 | use std::path::PathBuf; 6 | 7 | /// Configuration for building a smart contract. 8 | pub struct BuildContract { 9 | /// Path of the contract project. 10 | pub(crate) path: Option, 11 | /// Build profile: `true` for release mode, `false` for debug mode. 12 | pub(crate) release: bool, 13 | } 14 | 15 | impl BuildContract { 16 | /// Executes the command. 17 | pub(crate) fn execute(self) -> anyhow::Result<&'static str> { 18 | self.build(&mut cli::Cli) 19 | } 20 | 21 | /// Builds a smart contract 22 | /// 23 | /// # Arguments 24 | /// * `cli` - The CLI implementation to be used. 25 | fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { 26 | cli.intro("Building your contract")?; 27 | // Build contract. 28 | let build_result = 29 | build_smart_contract(self.path.as_deref(), self.release, Verbosity::Default)?; 30 | cli.success(build_result.display())?; 31 | cli.outro("Build completed successfully!")?; 32 | Ok("contract") 33 | } 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | use crate::cli::MockCli; 40 | use pop_contracts::{create_smart_contract, Contract::Standard}; 41 | use std::fs::create_dir_all; 42 | 43 | #[test] 44 | fn build_works() -> anyhow::Result<()> { 45 | let name = "flipper"; 46 | let temp_dir = tempfile::tempdir()?; 47 | let path = temp_dir.path(); 48 | create_dir_all(path.join(name))?; 49 | create_smart_contract(name, &path.join(name), &Standard)?; 50 | 51 | for release in [false, true] { 52 | let mut cli = MockCli::new() 53 | .expect_intro("Building your contract") 54 | .expect_outro("Build completed successfully!"); 55 | 56 | assert_eq!( 57 | BuildContract { path: Some(path.join(name)), release }.build(&mut cli)?, 58 | "contract" 59 | ); 60 | 61 | cli.verify()?; 62 | } 63 | 64 | Ok(()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/pop-cli/src/commands/build/parachain.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{ 4 | cli, 5 | common::runtime::Feature::{Benchmark, TryRuntime}, 6 | style::style, 7 | }; 8 | use pop_common::Profile; 9 | use pop_parachains::build_parachain; 10 | use std::path::PathBuf; 11 | 12 | use super::{PACKAGE, PARACHAIN}; 13 | 14 | // Configuration for building a parachain. 15 | pub struct BuildParachain { 16 | /// Directory path for your project. 17 | pub(crate) path: PathBuf, 18 | /// The package to be built. 19 | pub(crate) package: Option, 20 | /// Build profile. 21 | pub(crate) profile: Profile, 22 | /// Whether to build the parachain with `runtime-benchmarks` feature. 23 | pub(crate) benchmark: bool, 24 | /// Whether to build the parachain with `try-runtime` feature. 25 | pub(crate) try_runtime: bool, 26 | } 27 | 28 | impl BuildParachain { 29 | /// Executes the build process. 30 | pub(crate) fn execute(self) -> anyhow::Result<&'static str> { 31 | self.build(&mut cli::Cli) 32 | } 33 | 34 | /// Builds a parachain. 35 | /// 36 | /// # Arguments 37 | /// * `cli` - The CLI implementation to be used. 38 | fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { 39 | let project = if self.package.is_some() { PACKAGE } else { PARACHAIN }; 40 | 41 | // Enable the features based on the user's input. 42 | let mut features = vec![]; 43 | if self.benchmark { 44 | features.push(Benchmark.as_ref()); 45 | } 46 | if self.try_runtime { 47 | features.push(TryRuntime.as_ref()); 48 | } 49 | cli.intro(if features.is_empty() { 50 | format!("Building your {project}") 51 | } else { 52 | format!("Building your {project} with features: {}", features.join(",")) 53 | })?; 54 | 55 | if self.profile == Profile::Debug { 56 | cli.warning("NOTE: this command now defaults to DEBUG builds. Please use `--release` (or simply `-r`) for a release build...")?; 57 | } 58 | 59 | // Build parachain. 60 | cli.warning("NOTE: this may take some time...")?; 61 | let binary = build_parachain(&self.path, self.package, &self.profile, None, features)?; 62 | cli.info(format!("The {project} was built in {} mode.", self.profile))?; 63 | cli.outro("Build completed successfully!")?; 64 | let generated_files = [format!("Binary generated at: {}", binary.display())]; 65 | let generated_files: Vec<_> = generated_files 66 | .iter() 67 | .map(|s| style(format!("{} {s}", console::Emoji("●", ">"))).dim().to_string()) 68 | .collect(); 69 | cli.success(format!("Generated files:\n{}", generated_files.join("\n")))?; 70 | cli.outro(format!( 71 | "Need help? Learn more at {}\n", 72 | style("https://learn.onpop.io").magenta().underlined() 73 | ))?; 74 | 75 | Ok(project) 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use super::*; 82 | use cli::MockCli; 83 | use duct::cmd; 84 | use pop_common::manifest::{add_feature, add_production_profile}; 85 | use std::{fs, io::Write, path::Path}; 86 | use strum::VariantArray; 87 | 88 | // Function that generates a Cargo.toml inside node directory for testing. 89 | fn generate_mock_node(temp_dir: &Path) -> anyhow::Result<()> { 90 | // Create a node directory 91 | let target_dir = temp_dir.join("node"); 92 | fs::create_dir(&target_dir)?; 93 | // Create a Cargo.toml file 94 | let mut toml_file = fs::File::create(target_dir.join("Cargo.toml"))?; 95 | writeln!( 96 | toml_file, 97 | r#" 98 | [package] 99 | name = "hello_world" 100 | version = "0.1.0" 101 | 102 | [dependencies] 103 | "# 104 | )?; 105 | Ok(()) 106 | } 107 | 108 | #[test] 109 | fn build_works() -> anyhow::Result<()> { 110 | let name = "hello_world"; 111 | let temp_dir = tempfile::tempdir()?; 112 | let path = temp_dir.path(); 113 | let project_path = path.join(name); 114 | let features = &[Benchmark.as_ref(), TryRuntime.as_ref()]; 115 | cmd("cargo", ["new", name, "--bin"]).dir(&path).run()?; 116 | add_production_profile(&project_path)?; 117 | for feature in features { 118 | add_feature(&project_path, (feature.to_string(), vec![]))?; 119 | } 120 | generate_mock_node(&project_path)?; 121 | 122 | for package in [None, Some(name.to_string())] { 123 | for profile in Profile::VARIANTS { 124 | // Build without features. 125 | test_build(package.clone(), &project_path, profile, &[])?; 126 | 127 | // Build with one feature. 128 | test_build(package.clone(), &project_path, profile, &[Benchmark.as_ref()])?; 129 | 130 | // Build with multiple features. 131 | test_build(package.clone(), &project_path, profile, features)?; 132 | } 133 | } 134 | Ok(()) 135 | } 136 | 137 | fn test_build( 138 | package: Option, 139 | project_path: &PathBuf, 140 | profile: &Profile, 141 | features: &[&str], 142 | ) -> anyhow::Result<()> { 143 | let project = if package.is_some() { PACKAGE } else { PARACHAIN }; 144 | let mut cli = MockCli::new() 145 | .expect_intro(if features.is_empty() { 146 | format!("Building your {project}") 147 | } else { 148 | format!("Building your {project} with features: {}", features.join(",")) 149 | }) 150 | .expect_warning("NOTE: this may take some time...") 151 | .expect_info(format!("The {project} was built in {profile} mode.")) 152 | .expect_outro("Build completed successfully!"); 153 | 154 | if profile == &Profile::Debug { 155 | cli = cli.expect_warning("NOTE: this command now defaults to DEBUG builds. Please use `--release` (or simply `-r`) for a release build..."); 156 | } 157 | 158 | assert_eq!( 159 | BuildParachain { 160 | path: project_path.clone(), 161 | package: package.clone(), 162 | profile: profile.clone(), 163 | benchmark: features.contains(&Benchmark.as_ref()), 164 | try_runtime: features.contains(&TryRuntime.as_ref()), 165 | } 166 | .build(&mut cli)?, 167 | project 168 | ); 169 | cli.verify() 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /crates/pop-cli/src/commands/call/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use clap::{Args, Subcommand}; 4 | use std::fmt::{Display, Formatter, Result}; 5 | #[cfg(feature = "parachain")] 6 | pub(crate) mod chain; 7 | #[cfg(any(feature = "polkavm-contracts", feature = "wasm-contracts"))] 8 | pub(crate) mod contract; 9 | 10 | /// Arguments for calling a smart contract. 11 | #[derive(Args)] 12 | #[command(args_conflicts_with_subcommands = true)] 13 | pub(crate) struct CallArgs { 14 | #[command(subcommand)] 15 | pub command: Command, 16 | } 17 | 18 | /// Call a chain or a smart contract. 19 | #[derive(Subcommand)] 20 | pub(crate) enum Command { 21 | /// Call a chain 22 | #[cfg(feature = "parachain")] 23 | #[clap(alias = "p", visible_aliases = ["parachain"])] 24 | Chain(chain::CallChainCommand), 25 | /// Call a contract 26 | #[cfg(any(feature = "polkavm-contracts", feature = "wasm-contracts"))] 27 | #[clap(alias = "c")] 28 | Contract(contract::CallContractCommand), 29 | } 30 | 31 | impl Display for Command { 32 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 33 | match self { 34 | #[cfg(feature = "parachain")] 35 | Command::Chain(_) => write!(f, "chain"), 36 | #[cfg(any(feature = "polkavm-contracts", feature = "wasm-contracts"))] 37 | Command::Contract(_) => write!(f, "contract"), 38 | } 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn command_display_works() { 48 | #[cfg(feature = "parachain")] 49 | assert_eq!(Command::Chain(Default::default()).to_string(), "chain"); 50 | #[cfg(any(feature = "polkavm-contracts", feature = "wasm-contracts"))] 51 | assert_eq!(Command::Contract(Default::default()).to_string(), "contract"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/pop-cli/src/commands/new/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use clap::{Args, Subcommand}; 4 | use std::fmt::{Display, Formatter, Result}; 5 | 6 | #[cfg(any(feature = "polkavm-contracts", feature = "wasm-contracts"))] 7 | pub mod contract; 8 | #[cfg(feature = "parachain")] 9 | pub mod pallet; 10 | #[cfg(feature = "parachain")] 11 | pub mod parachain; 12 | 13 | /// The possible values from the variants of an enum. 14 | #[macro_export] 15 | macro_rules! enum_variants { 16 | ($e: ty) => {{ 17 | PossibleValuesParser::new( 18 | <$e>::VARIANTS 19 | .iter() 20 | .map(|p| PossibleValue::new(p.as_ref())) 21 | .collect::>(), 22 | ) 23 | .try_map(|s| <$e>::from_str(&s).map_err(|e| format!("could not convert from {s} to type"))) 24 | }}; 25 | } 26 | 27 | /// Arguments for generating a new project. 28 | #[derive(Args)] 29 | #[command(args_conflicts_with_subcommands = true)] 30 | pub struct NewArgs { 31 | #[command(subcommand)] 32 | pub command: Command, 33 | } 34 | 35 | /// Generate a new parachain, pallet or smart contract. 36 | #[derive(Subcommand)] 37 | pub enum Command { 38 | /// Generate a new parachain 39 | #[cfg(feature = "parachain")] 40 | #[clap(alias = "p")] 41 | Parachain(parachain::NewParachainCommand), 42 | /// Generate a new pallet 43 | #[cfg(feature = "parachain")] 44 | #[clap(alias = "P")] 45 | Pallet(pallet::NewPalletCommand), 46 | /// Generate a new smart contract 47 | #[cfg(any(feature = "polkavm-contracts", feature = "wasm-contracts"))] 48 | #[clap(alias = "c")] 49 | Contract(contract::NewContractCommand), 50 | } 51 | 52 | impl Display for Command { 53 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 54 | match self { 55 | #[cfg(feature = "parachain")] 56 | Command::Parachain(_) => write!(f, "chain"), 57 | #[cfg(feature = "parachain")] 58 | Command::Pallet(_) => write!(f, "pallet"), 59 | #[cfg(any(feature = "polkavm-contracts", feature = "wasm-contracts"))] 60 | Command::Contract(_) => write!(f, "contract"), 61 | } 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | 69 | #[test] 70 | fn command_display_works() { 71 | #[cfg(feature = "parachain")] 72 | assert_eq!(Command::Parachain(Default::default()).to_string(), "chain"); 73 | #[cfg(feature = "parachain")] 74 | assert_eq!(Command::Pallet(Default::default()).to_string(), "pallet"); 75 | #[cfg(any(feature = "polkavm-contracts", feature = "wasm-contracts"))] 76 | assert_eq!(Command::Contract(Default::default()).to_string(), "contract"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/pop-cli/src/commands/test/contract.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{ 4 | cli, 5 | common::{ 6 | contracts::check_contracts_node_and_prompt, 7 | TestFeature::{self, *}, 8 | }, 9 | }; 10 | use clap::Args; 11 | use pop_common::test_project; 12 | use pop_contracts::test_e2e_smart_contract; 13 | use std::path::PathBuf; 14 | 15 | const HELP_HEADER: &str = "Smart contract testing options"; 16 | 17 | #[derive(Args, Default)] 18 | #[clap(next_help_heading = HELP_HEADER)] 19 | pub(crate) struct TestContractCommand { 20 | #[clap(skip)] 21 | pub(crate) path: Option, 22 | /// Run end-to-end tests 23 | #[arg(short, long)] 24 | e2e: bool, 25 | #[arg(short, long, help = "Path to the contracts node to run e2e tests [default: none]")] 26 | node: Option, 27 | /// Automatically source the needed binary required without prompting for confirmation. 28 | #[clap(short = 'y', long)] 29 | skip_confirm: bool, 30 | } 31 | 32 | impl TestContractCommand { 33 | /// Executes the command. 34 | pub(crate) async fn execute( 35 | mut self, 36 | cli: &mut impl cli::traits::Cli, 37 | ) -> anyhow::Result { 38 | if self.e2e { 39 | cli.intro("Starting end-to-end tests")?; 40 | 41 | self.node = match check_contracts_node_and_prompt( 42 | cli, 43 | &crate::cache()?, 44 | self.skip_confirm, 45 | ) 46 | .await 47 | { 48 | Ok(binary_path) => Some(binary_path), 49 | Err(_) => { 50 | cli.warning("🚫 substrate-contracts-node is necessary to run e2e tests. Will try to run tests anyway...")?; 51 | Some(PathBuf::new()) 52 | }, 53 | }; 54 | 55 | test_e2e_smart_contract(self.path.as_deref(), self.node.as_deref())?; 56 | cli.outro("End-to-end testing complete")?; 57 | Ok(E2e) 58 | } else { 59 | cli.intro("Starting unit tests")?; 60 | test_project(self.path.as_deref())?; 61 | cli.outro("Unit testing complete")?; 62 | Ok(Unit) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/pop-cli/src/common/builds.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use std::path::PathBuf; 4 | 5 | #[cfg(feature = "parachain")] 6 | use { 7 | crate::cli::traits::{Cli, Select}, 8 | pop_common::Profile, 9 | pop_parachains::{binary_path, build_parachain}, 10 | std::path::Path, 11 | strum::{EnumMessage, VariantArray}, 12 | }; 13 | 14 | /// This method is used to get the proper project path format (with or without cli flag) 15 | pub fn get_project_path(path_flag: Option, path_pos: Option) -> Option { 16 | let project_path = if let Some(ref path) = path_pos { 17 | Some(path) // Use positional path if present 18 | } else { 19 | path_flag.as_ref() // Otherwise, use the named path 20 | }; 21 | project_path.cloned() 22 | } 23 | 24 | /// Locate node binary, if it doesn't exist trigger build. 25 | /// 26 | /// # Arguments 27 | /// * `cli`: Command line interface. 28 | /// * `project_path`: The project path. 29 | /// * `mode`: The profile to use for building. 30 | /// * `features`: The features that node is built with. 31 | #[cfg(feature = "parachain")] 32 | pub fn ensure_node_binary_exists( 33 | cli: &mut impl Cli, 34 | project_path: &Path, 35 | mode: &Profile, 36 | features: Vec<&str>, 37 | ) -> anyhow::Result { 38 | match binary_path(&mode.target_directory(project_path), &project_path.join("node")) { 39 | Ok(binary_path) => Ok(binary_path), 40 | _ => { 41 | cli.info("Node was not found. The project will be built locally.".to_string())?; 42 | cli.warning("NOTE: this may take some time...")?; 43 | build_parachain(project_path, None, mode, None, features).map_err(|e| e.into()) 44 | }, 45 | } 46 | } 47 | 48 | /// Guide the user to select a build profile. 49 | /// 50 | /// # Arguments 51 | /// * `cli`: Command line interface. 52 | #[cfg(feature = "parachain")] 53 | pub fn guide_user_to_select_profile(cli: &mut impl Cli) -> anyhow::Result { 54 | let default = Profile::Release; 55 | // Prompt for build profile. 56 | let mut prompt = cli 57 | .select("Choose the build profile of the binary that should be used: ".to_string()) 58 | .initial_value(&default); 59 | for profile in Profile::VARIANTS { 60 | prompt = prompt.item( 61 | profile, 62 | profile.get_message().unwrap_or(profile.as_ref()), 63 | profile.get_detailed_message().unwrap_or_default(), 64 | ); 65 | } 66 | Ok(prompt.interact()?.clone()) 67 | } 68 | 69 | #[cfg(test)] 70 | #[cfg(feature = "parachain")] 71 | mod tests { 72 | use std::fs::{self, File}; 73 | 74 | use super::*; 75 | use crate::cli::MockCli; 76 | use duct::cmd; 77 | use tempfile::tempdir; 78 | 79 | #[test] 80 | #[cfg(feature = "parachain")] 81 | fn guide_user_to_select_profile_works() -> anyhow::Result<()> { 82 | let mut cli = MockCli::new().expect_select( 83 | "Choose the build profile of the binary that should be used: ".to_string(), 84 | Some(true), 85 | true, 86 | Some(Profile::get_variants()), 87 | 0, 88 | None, 89 | ); 90 | guide_user_to_select_profile(&mut cli)?; 91 | cli.verify() 92 | } 93 | 94 | #[test] 95 | #[cfg(feature = "parachain")] 96 | fn ensure_node_binary_exists_works() -> anyhow::Result<()> { 97 | let mut cli = MockCli::new(); 98 | let name = "node"; 99 | let temp_dir = tempdir()?; 100 | cmd("cargo", ["new", name, "--bin"]).dir(temp_dir.path()).run()?; 101 | let target_path = Profile::Release.target_directory(temp_dir.path()); 102 | 103 | fs::create_dir(&temp_dir.path().join("target"))?; 104 | fs::create_dir(&target_path)?; 105 | File::create(target_path.join("node"))?; 106 | 107 | let binary_path = 108 | ensure_node_binary_exists(&mut cli, temp_dir.path(), &Profile::Release, vec![])?; 109 | assert_eq!(binary_path, target_path.join("node")); 110 | cli.verify() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /crates/pop-cli/src/common/chain.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::cli::traits::*; 4 | use anyhow::{anyhow, Result}; 5 | use pop_parachains::{parse_chain_metadata, set_up_client, OnlineClient, Pallet, SubstrateConfig}; 6 | use url::Url; 7 | 8 | // Represents a chain and its associated metadata. 9 | pub(crate) struct Chain { 10 | // Websocket endpoint of the node. 11 | pub url: Url, 12 | // The client used to interact with the chain. 13 | pub client: OnlineClient, 14 | // A list of pallets available on the chain. 15 | pub pallets: Vec, 16 | } 17 | 18 | // Configures a chain by resolving the URL and fetching its metadata. 19 | pub(crate) async fn configure( 20 | input_message: &str, 21 | default_input: &str, 22 | url: &Option, 23 | cli: &mut impl Cli, 24 | ) -> Result { 25 | // Resolve url. 26 | let url = match url { 27 | Some(url) => url.clone(), 28 | None => { 29 | // Prompt for url. 30 | let url: String = cli.input(input_message).default_input(default_input).interact()?; 31 | Url::parse(&url)? 32 | }, 33 | }; 34 | let client = set_up_client(url.as_str()).await?; 35 | let pallets = get_pallets(&client).await?; 36 | Ok(Chain { url, client, pallets }) 37 | } 38 | 39 | // Get available pallets on the chain. 40 | pub(crate) async fn get_pallets(client: &OnlineClient) -> Result> { 41 | // Parse metadata from chain url. 42 | let mut pallets = parse_chain_metadata(client) 43 | .map_err(|e| anyhow!(format!("Unable to fetch the chain metadata: {}", e.to_string())))?; 44 | // Sort by name for display. 45 | pallets.sort_by_key(|pallet| pallet.name.clone()); 46 | pallets.iter_mut().for_each(|p| p.functions.sort_by_key(|f| f.name.clone())); 47 | Ok(pallets) 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | use crate::cli::MockCli; 54 | 55 | const POP_NETWORK_TESTNET_URL: &str = "wss://rpc2.paseo.popnetwork.xyz"; 56 | 57 | #[tokio::test] 58 | async fn configure_works() -> Result<()> { 59 | let message = "Enter the URL of the chain:"; 60 | let mut cli = MockCli::new().expect_input(message, POP_NETWORK_TESTNET_URL.into()); 61 | let chain = configure(message, POP_NETWORK_TESTNET_URL, &None, &mut cli).await?; 62 | assert_eq!(chain.url, Url::parse(POP_NETWORK_TESTNET_URL)?); 63 | cli.verify() 64 | } 65 | 66 | #[tokio::test] 67 | async fn get_pallets_works() -> Result<()> { 68 | let client = set_up_client(POP_NETWORK_TESTNET_URL).await?; 69 | let pallets = get_pallets(&client).await?; 70 | assert!(!pallets.is_empty()); 71 | Ok(()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /crates/pop-cli/src/common/helpers.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | /// A macro to facilitate the select multiple variant of an enum and store them inside a `Vec`. 4 | /// # Arguments 5 | /// * `$enum`: The enum type to be iterated over for the selection. This enum must implement 6 | /// `IntoEnumIterator` and `EnumMessage` traits from the `strum` crate. Each variant is 7 | /// responsible of its own messages. 8 | /// * `$prompt_message`: The message displayed to the user. It must implement the `Display` trait. 9 | /// * `$excluded_variants`: If the enum contain variants that shouldn't be included in the 10 | /// multiselect pick, they're specified here. This is useful if a enum is used in a few places and 11 | /// not all of them need all the variants but share some of them. It has to be a `Vec`; 12 | /// # Note 13 | /// This macro only works with a 1-byte sized enums, this is, fieldless enums with at most 255 14 | /// elements each. This is because we're just interested in letting the user to pick some options 15 | /// among a predefined set, then the name should be descriptive enough, and 1-byte sized enums are 16 | /// really easy to convert to and from a `u8`, so we can work with `u8` all the time and just 17 | /// recover the variant at the end. 18 | /// 19 | /// The decision of using 1-byte enums instead of just fieldless enums is for simplicity: we won't 20 | /// probably offer a user to pick from > 256 options. If this macro is used with enums containing 21 | /// fields, the conversion to `u8` will simply be detected at compile time and the compilation will 22 | /// fail. If this macro is used with fieldless enums greater than 1-byte (really weird but 23 | /// possible), the conversion to u8 will overflow and lead to unexpected behavior, so we panic at 24 | /// runtime if that happens for completeness. 25 | /// 26 | /// # Example 27 | /// 28 | /// ```rust 29 | /// use strum::{IntoEnumIterator, EnumMessage}; 30 | /// use strum_macros::{EnumIter, EnumMessage as EnumMessageDerive}; 31 | /// use cliclack::{multiselect}; 32 | /// use pop_common::multiselect_pick; 33 | /// 34 | /// #[derive(Debug, EnumIter, EnumMessageDerive, Copy, Clone)] 35 | /// enum FieldlessEnum { 36 | /// #[strum(message = "Type 1", detailed_message = "Detailed message for Type 1")] 37 | /// Type1, 38 | /// #[strum(message = "Type 2", detailed_message = "Detailed message for Type 2")] 39 | /// Type2, 40 | /// #[strum(message = "Type 3", detailed_message = "Detailed message for Type 3")] 41 | /// Type3, 42 | /// } 43 | /// 44 | /// fn test_function() -> Result<(),std::io::Error>{ 45 | /// let vec = multiselect_pick!(FieldlessEnum, "Hello, world!"); 46 | /// Ok(()) 47 | /// } 48 | /// ``` 49 | /// 50 | /// # Requirements 51 | /// 52 | /// This macro requires the following imports to function correctly: 53 | /// 54 | /// ```rust 55 | /// use cliclack::{multiselect}; 56 | /// use strum::{EnumMessage, IntoEnumIterator}; 57 | /// ``` 58 | /// 59 | /// Additionally, this macro handle results, so it must be used inside a function doing so. 60 | /// Otherwise the compilation will fail. 61 | #[macro_export] 62 | macro_rules! multiselect_pick { 63 | ($enum: ty, $prompt_message: expr $(, $excluded_variants: expr)?) => {{ 64 | // Ensure the enum is 1-byte long. This is needed cause fieldless enums with > 256 elements 65 | // will lead to unexpected behavior as the conversion to u8 for them isn't detected as wrong 66 | // at compile time. Enums containing variants with fields will be catched at compile time. 67 | // Weird but possible. 68 | assert_eq!(std::mem::size_of::<$enum>(), 1); 69 | let mut prompt = multiselect(format!( 70 | "{} {}", 71 | $prompt_message, 72 | "Pick an option by pressing the spacebar. Press enter when you're done!" 73 | )) 74 | .required(false); 75 | 76 | for variant in <$enum>::iter() { 77 | $(if $excluded_variants.contains(&variant){continue; })? 78 | prompt = prompt.item( 79 | variant as u8, 80 | variant.get_message().unwrap_or_default(), 81 | variant.get_detailed_message().unwrap_or_default(), 82 | ); 83 | } 84 | 85 | // The unsafe block is safe cause the bytes are the discriminants of the enum picked above, 86 | // qed; 87 | prompt 88 | .interact()? 89 | .iter() 90 | .map(|byte| unsafe { std::mem::transmute(*byte) }) 91 | .collect::>() 92 | }}; 93 | } 94 | -------------------------------------------------------------------------------- /crates/pop-cli/src/common/prompt.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::cli::traits::Cli; 4 | use anyhow::Result; 5 | 6 | // Displays a message to the user, with formatting based on the success status. 7 | #[allow(dead_code)] 8 | pub(crate) fn display_message(message: &str, success: bool, cli: &mut impl Cli) -> Result<()> { 9 | if success { 10 | cli.outro(message)?; 11 | } else { 12 | cli.outro_cancel(message)?; 13 | } 14 | Ok(()) 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use super::display_message; 20 | use crate::cli::MockCli; 21 | use anyhow::Result; 22 | 23 | #[test] 24 | fn display_message_works() -> Result<()> { 25 | let mut cli = MockCli::new().expect_outro(&"Call completed successfully!"); 26 | display_message("Call completed successfully!", true, &mut cli)?; 27 | cli.verify()?; 28 | let mut cli = MockCli::new().expect_outro_cancel("Call failed."); 29 | display_message("Call failed.", false, &mut cli)?; 30 | cli.verify() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/pop-cli/src/common/wallet.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{ 4 | cli::traits::Cli, 5 | wallet_integration::{ 6 | FrontendFromString, SubmitRequest, TransactionData, WalletIntegrationManager, 7 | }, 8 | }; 9 | use cliclack::{log, spinner}; 10 | #[cfg(feature = "parachain")] 11 | use { 12 | anyhow::{anyhow, Result}, 13 | pop_parachains::{ 14 | parse_and_format_events, submit_signed_extrinsic, ExtrinsicEvents, OnlineClient, 15 | SubstrateConfig, 16 | }, 17 | url::Url, 18 | }; 19 | 20 | /// The prompt to ask the user if they want to use the wallet for signing. 21 | pub const USE_WALLET_PROMPT: &str = "Do you want to use your browser wallet to sign the extrinsic? (Selecting 'No' will prompt you to manually enter the secret key URI for signing, e.g., '//Alice')"; 22 | 23 | /// Launches the wallet integration for in-browser signing. Blocks until the signature is received. 24 | /// 25 | /// # Arguments 26 | /// * `call_data` - The call data to be signed. 27 | /// * `url` - Chain rpc. 28 | /// # Returns 29 | /// * The signed payload and the associated contract address, if provided by the wallet. 30 | pub async fn request_signature(call_data: Vec, rpc: String) -> anyhow::Result { 31 | let ui = FrontendFromString::new(include_str!("../assets/index.html").to_string()); 32 | 33 | let transaction_data = TransactionData::new(rpc, call_data); 34 | // Starts server with port 9090. 35 | let mut wallet = WalletIntegrationManager::new(ui, transaction_data, Some(9090)); 36 | let url = format!("http://{}", &wallet.server_url); 37 | log::step(format!("Wallet signing portal started at {url}."))?; 38 | 39 | let spinner = spinner(); 40 | spinner.start(format!("Opening browser to {url}")); 41 | if let Err(e) = open::that(url) { 42 | spinner.error(format!("Failed to launch browser. Please open link manually. {e}")); 43 | } 44 | 45 | spinner.start("Waiting for signature... Press Ctrl+C to terminate early."); 46 | loop { 47 | // Display error, if any. 48 | if let Some(error) = wallet.take_error().await { 49 | log::error(format!("Signing portal error: {error}"))?; 50 | } 51 | 52 | let state = wallet.state.lock().await; 53 | // If the payload is submitted we terminate the frontend. 54 | if !wallet.is_running() || state.signed_payload.is_some() { 55 | wallet.task_handle.await??; 56 | break; 57 | } 58 | } 59 | spinner.stop(""); 60 | 61 | let signed_payload = wallet.state.lock().await.signed_payload.take(); 62 | let contract_address = wallet.state.lock().await.contract_address.take(); 63 | 64 | Ok(SubmitRequest { signed_payload, contract_address }) 65 | } 66 | 67 | /// Prompts the user to use the wallet for signing. 68 | /// # Arguments 69 | /// * `cli` - The CLI instance. 70 | /// # Returns 71 | /// * `true` if the user wants to use the wallet, `false` otherwise. 72 | pub fn prompt_to_use_wallet(cli: &mut impl Cli) -> anyhow::Result { 73 | use crate::cli::traits::Confirm; 74 | 75 | if cli.confirm(USE_WALLET_PROMPT).initial_value(true).interact()? { 76 | Ok(true) 77 | } else { 78 | Ok(false) 79 | } 80 | } 81 | 82 | // Sign and submit an extrinsic using wallet integration. 83 | #[cfg(feature = "parachain")] 84 | pub(crate) async fn submit_extrinsic( 85 | client: &OnlineClient, 86 | url: &Url, 87 | call_data: Vec, 88 | cli: &mut impl Cli, 89 | ) -> Result> { 90 | let maybe_payload = request_signature(call_data, url.to_string()).await?.signed_payload; 91 | let payload = maybe_payload.ok_or_else(|| anyhow!("No signed payload received."))?; 92 | cli.success("Signed payload received.")?; 93 | let spinner = cliclack::spinner(); 94 | spinner.start("Submitting the extrinsic and waiting for finalization, please be patient..."); 95 | 96 | let result = submit_signed_extrinsic(client.clone(), payload) 97 | .await 98 | .map_err(anyhow::Error::from)?; 99 | 100 | let events = parse_and_format_events(client, url, &result).await?; 101 | spinner.stop(format!( 102 | "Extrinsic submitted with hash: {:?}\n{}", 103 | result.extrinsic_hash(), 104 | events 105 | )); 106 | Ok(result) 107 | } 108 | -------------------------------------------------------------------------------- /crates/pop-cli/src/style.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use cliclack::ThemeState; 4 | pub(crate) use console::style; 5 | use console::Style; 6 | 7 | pub(crate) fn get_styles() -> clap::builder::Styles { 8 | use clap::builder::styling::{AnsiColor, Color, Style}; 9 | clap::builder::Styles::styled() 10 | .usage(Style::new().bold().fg_color(Some(Color::Ansi(AnsiColor::BrightCyan)))) 11 | .header(Style::new().bold().fg_color(Some(Color::Ansi(AnsiColor::BrightCyan)))) 12 | .literal(Style::new().fg_color(Some(Color::Ansi(AnsiColor::BrightMagenta)))) 13 | .invalid(Style::new().bold().fg_color(Some(Color::Ansi(AnsiColor::Red)))) 14 | .error(Style::new().bold().fg_color(Some(Color::Ansi(AnsiColor::Red)))) 15 | .valid( 16 | Style::new() 17 | .bold() 18 | .underline() 19 | .fg_color(Some(Color::Ansi(AnsiColor::BrightMagenta))), 20 | ) 21 | .placeholder(Style::new().fg_color(Some(Color::Ansi(AnsiColor::White)))) 22 | } 23 | 24 | pub(crate) struct Theme; 25 | 26 | impl cliclack::Theme for Theme { 27 | fn bar_color(&self, state: &ThemeState) -> Style { 28 | match state { 29 | ThemeState::Active => Style::new().bright().magenta(), 30 | ThemeState::Error(_) => Style::new().bright().red(), 31 | _ => Style::new().magenta().dim(), 32 | } 33 | } 34 | 35 | fn state_symbol_color(&self, _state: &ThemeState) -> Style { 36 | Style::new().bright().magenta() 37 | } 38 | 39 | fn info_symbol(&self) -> String { 40 | "⚙".into() 41 | } 42 | } 43 | 44 | /// Formats a URL with bold and underlined style. 45 | #[cfg(feature = "parachain")] 46 | pub(crate) fn format_url(url: &str) -> String { 47 | format!("{}", style(url).bold().underlined()) 48 | } 49 | 50 | /// Formats the step label if steps should be shown. 51 | #[cfg(feature = "parachain")] 52 | pub(crate) fn format_step_prefix(current: usize, total: usize, show: bool) -> String { 53 | show.then(|| format!("[{}/{}]: ", current, total)).unwrap_or_default() 54 | } 55 | 56 | #[cfg(test)] 57 | #[cfg(feature = "parachain")] 58 | mod tests { 59 | use super::*; 60 | use console::Style; 61 | 62 | #[test] 63 | fn format_provider_url_works() { 64 | let url = "https://example.com"; 65 | assert_eq!(format_url(url), format!("{}", Style::new().bold().underlined().apply_to(url))); 66 | } 67 | 68 | #[test] 69 | fn format_step_prefix_works() { 70 | assert_eq!(format_step_prefix(2, 5, true), "[2/5]: "); 71 | assert_eq!(format_step_prefix(2, 5, false), ""); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /crates/pop-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pop-common" 3 | description = "Library that provides a collection of essential utilities and shared functionality for pop." 4 | readme = "README.md" 5 | version.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | 10 | [dependencies] 11 | anyhow.workspace = true 12 | bytes.workspace = true 13 | cargo_toml.workspace = true 14 | contract-build.workspace = true 15 | contract-extrinsics.workspace = true 16 | duct.workspace = true 17 | flate2.workspace = true 18 | git2.workspace = true 19 | git2_credentials.workspace = true 20 | ink_env.workspace = true 21 | regex.workspace = true 22 | reqwest.workspace = true 23 | scale-info.workspace = true 24 | serde_json.workspace = true 25 | serde.workspace = true 26 | sp-core.workspace = true 27 | strum.workspace = true 28 | strum_macros.workspace = true 29 | subxt.workspace = true 30 | subxt-signer.workspace = true 31 | tar.workspace = true 32 | tempfile.workspace = true 33 | thiserror.workspace = true 34 | tokio.workspace = true 35 | toml.workspace = true 36 | toml_edit.workspace = true 37 | url.workspace = true 38 | 39 | [dev-dependencies] 40 | mockito.workspace = true 41 | tempfile.workspace = true 42 | -------------------------------------------------------------------------------- /crates/pop-common/README.md: -------------------------------------------------------------------------------- 1 | # pop-common 2 | 3 | A crate that provides a collection of essential utilities and shared functionality for pop. Used by 4 | [`pop-cli`](https://github.com/r0gue-io/pop-cli), [`pop-parachains`](https://github.com/r0gue-io/pop-cli/tree/main/crates/pop-parachains) and [`pop-contracts`](https://github.com/r0gue-io/pop-cli/tree/main/crates/pop-contracts). -------------------------------------------------------------------------------- /crates/pop-common/src/account_id.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{Config, DefaultConfig, Error}; 4 | use sp_core::keccak_256; 5 | use std::str::FromStr; 6 | use subxt::utils::{to_hex, H160}; 7 | 8 | /// Parses an account ID from its string representation. 9 | /// 10 | /// # Arguments 11 | /// * `account` - A string representing the account ID to parse. 12 | pub fn parse_account(account: &str) -> Result<::AccountId, Error> { 13 | ::AccountId::from_str(account) 14 | .map_err(|e| Error::AccountAddressParsing(format!("{}", e))) 15 | } 16 | 17 | /// Parses a H160 account from its string representation. 18 | /// 19 | /// # Arguments 20 | /// * `account` - A hex-encoded string representation. 21 | pub fn parse_h160_account(account: &str) -> Result { 22 | let bytes = contract_build::util::decode_hex(account) 23 | .map_err(|e| Error::AccountAddressParsing(format!("Invalid hex: {}", e)))?; 24 | 25 | if bytes.len() != 20 { 26 | return Err(Error::AccountAddressParsing(format!( 27 | "H160 must be 20 bytes in length, got {}", 28 | bytes.len() 29 | ))); 30 | } 31 | Ok(H160::from_slice(&bytes[..])) 32 | } 33 | 34 | /// Converts a list of accounts into EVM-compatible `AccountId20`. 35 | /// 36 | /// # Arguments 37 | /// * `accounts` - A vector of `AccountId32` strings. 38 | pub fn convert_to_evm_accounts(accounts: Vec) -> Result, Error> { 39 | accounts 40 | .into_iter() 41 | .map(|account| { 42 | let account_id = parse_account(&account)?.0; 43 | let evm_account = AccountIdMapper::to_address(&account_id); 44 | Ok(to_hex(evm_account)) 45 | }) 46 | .collect() 47 | } 48 | 49 | // Logic copied from `cargo-contract` for `AccountId` to `H160` mapping: 50 | // https://github.com/use-ink/cargo-contract/blob/master/crates/extrinsics/src/lib.rs#L332 51 | pub(crate) struct AccountIdMapper {} 52 | impl AccountIdMapper { 53 | pub fn to_address(account_id: &[u8]) -> H160 { 54 | let mut account_bytes: [u8; 32] = [0u8; 32]; 55 | account_bytes.copy_from_slice(&account_id[..32]); 56 | if Self::is_eth_derived(account_id) { 57 | // this was originally an eth address 58 | // we just strip the 0xEE suffix to get the original address 59 | H160::from_slice(&account_bytes[..20]) 60 | } else { 61 | // this is an (ed|sr)25510 derived address 62 | // avoid truncating the public key by hashing it first 63 | let account_hash = keccak_256(account_bytes.as_ref()); 64 | H160::from_slice(&account_hash[12..]) 65 | } 66 | } 67 | 68 | /// Returns true if the passed account id is controlled by an Ethereum key. 69 | /// 70 | /// This is a stateless check that just compares the last 12 bytes. Please note that 71 | /// it is theoretically possible to create an ed25519 keypair that passed this 72 | /// filter. However, this can't be used for an attack. It also won't happen by 73 | /// accident since everbody is using sr25519 where this is not a valid public key. 74 | //fn is_eth_derived(account_id: &[u8]) -> bool { 75 | fn is_eth_derived(account_bytes: &[u8]) -> bool { 76 | account_bytes[20..] == [0xEE; 12] 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use super::*; 83 | use anyhow::Result; 84 | 85 | #[test] 86 | fn parse_account_works() -> Result<(), Error> { 87 | let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?; 88 | assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A"); 89 | Ok(()) 90 | } 91 | 92 | #[test] 93 | fn parse_account_fails_wrong_value() -> Result<(), Error> { 94 | assert!(matches!( 95 | parse_account("5CLPm1CeUvJhZ8GCDZCR7"), 96 | Err(super::Error::AccountAddressParsing(..)) 97 | )); 98 | assert!(matches!( 99 | parse_account("wrongaccount"), 100 | Err(super::Error::AccountAddressParsing(..)) 101 | )); 102 | Ok(()) 103 | } 104 | 105 | #[test] 106 | fn parse_h160_account_works() -> Result<(), Error> { 107 | let addr = "0x48550a4bb374727186c55365b7c9c0a1a31bdafe"; 108 | let parsed = parse_h160_account(addr)?; 109 | assert_eq!(to_hex(parsed), addr.to_lowercase()); 110 | Ok(()) 111 | } 112 | 113 | #[test] 114 | fn parse_h160_account_fails_on_invalid_hex() -> Result<(), Error> { 115 | let invalid_hex = "wrongaccount"; 116 | assert!(matches!( 117 | parse_h160_account(invalid_hex), 118 | Err(Error::AccountAddressParsing(msg)) if msg.contains("Invalid hex") 119 | )); 120 | Ok(()) 121 | } 122 | 123 | #[test] 124 | fn convert_to_evm_accounts_works() -> Result<()> { 125 | let accounts = vec![ 126 | "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".to_string(), 127 | "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".to_string(), 128 | ]; 129 | let evm_accounts = convert_to_evm_accounts(accounts)?; 130 | assert_eq!( 131 | evm_accounts, 132 | vec![ 133 | "0x9621dde636de098b43efb0fa9b61facfe328f99d".to_string(), 134 | "0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01".to_string(), 135 | ] 136 | ); 137 | Ok(()) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /crates/pop-common/src/build.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use std::{ 4 | fmt, 5 | path::{Path, PathBuf}, 6 | }; 7 | use strum::{EnumMessage as _, VariantArray as _}; 8 | use strum_macros::{AsRefStr, EnumMessage, EnumString, VariantArray}; 9 | 10 | /// Enum representing a build profile. 11 | #[derive(AsRefStr, Clone, Default, Debug, EnumString, EnumMessage, VariantArray, Eq, PartialEq)] 12 | pub enum Profile { 13 | /// Debug profile, optimized for debugging. 14 | #[strum(serialize = "debug", message = "Debug", detailed_message = "Optimized for debugging.")] 15 | Debug, 16 | /// Release profile, optimized without any debugging functionality. 17 | #[default] 18 | #[strum( 19 | serialize = "release", 20 | message = "Release", 21 | detailed_message = "Optimized without any debugging functionality." 22 | )] 23 | Release, 24 | /// Production profile, optimized for ultimate performance. 25 | #[strum( 26 | serialize = "production", 27 | message = "Production", 28 | detailed_message = "Optimized for ultimate performance." 29 | )] 30 | Production, 31 | } 32 | 33 | impl Profile { 34 | /// Returns the corresponding path to the target directory. 35 | pub fn target_directory(&self, path: &Path) -> PathBuf { 36 | match self { 37 | Profile::Release => path.join("target/release"), 38 | Profile::Debug => path.join("target/debug"), 39 | Profile::Production => path.join("target/production"), 40 | } 41 | } 42 | 43 | /// Returns the variants of the enum. 44 | pub fn get_variants() -> Vec<(String, String)> { 45 | Profile::VARIANTS 46 | .iter() 47 | .map(|profile| { 48 | ( 49 | profile.get_message().unwrap_or(profile.as_ref()).to_string(), 50 | profile.get_detailed_message().unwrap_or_default().to_string(), 51 | ) 52 | }) 53 | .collect() 54 | } 55 | } 56 | 57 | impl From for bool { 58 | fn from(value: Profile) -> Self { 59 | value != Profile::Debug 60 | } 61 | } 62 | 63 | impl From for Profile { 64 | fn from(value: bool) -> Self { 65 | if value { 66 | Profile::Release 67 | } else { 68 | Profile::Debug 69 | } 70 | } 71 | } 72 | 73 | impl fmt::Display for Profile { 74 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 75 | match self { 76 | Self::Debug => write!(f, "debug"), 77 | Self::Release => write!(f, "release"), 78 | Self::Production => write!(f, "production"), 79 | } 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | use std::path::Path; 87 | use strum::EnumMessage; 88 | 89 | #[test] 90 | fn profile_from_string() { 91 | assert_eq!("debug".parse::().unwrap(), Profile::Debug); 92 | assert_eq!("release".parse::().unwrap(), Profile::Release); 93 | assert_eq!("production".parse::().unwrap(), Profile::Production); 94 | } 95 | 96 | #[test] 97 | fn profile_detailed_message() { 98 | assert_eq!(Profile::Debug.get_detailed_message(), Some("Optimized for debugging.")); 99 | assert_eq!( 100 | Profile::Release.get_detailed_message(), 101 | Some("Optimized without any debugging functionality.") 102 | ); 103 | assert_eq!( 104 | Profile::Production.get_detailed_message(), 105 | Some("Optimized for ultimate performance.") 106 | ); 107 | } 108 | 109 | #[test] 110 | fn profile_target_directory() { 111 | let base_path = Path::new("/example/path"); 112 | 113 | assert_eq!( 114 | Profile::Debug.target_directory(base_path), 115 | Path::new("/example/path/target/debug") 116 | ); 117 | assert_eq!( 118 | Profile::Release.target_directory(base_path), 119 | Path::new("/example/path/target/release") 120 | ); 121 | assert_eq!( 122 | Profile::Production.target_directory(base_path), 123 | Path::new("/example/path/target/production") 124 | ); 125 | } 126 | 127 | #[test] 128 | fn profile_default() { 129 | let default_profile = Profile::default(); 130 | assert_eq!(default_profile, Profile::Release); 131 | } 132 | 133 | #[test] 134 | fn profile_from_bool() { 135 | assert_eq!(Profile::from(true), Profile::Release); 136 | assert_eq!(Profile::from(false), Profile::Debug); 137 | } 138 | 139 | #[test] 140 | fn profile_into_bool() { 141 | assert_eq!(bool::from(Profile::Debug), false); 142 | assert_eq!(bool::from(Profile::Release), true); 143 | assert_eq!(bool::from(Profile::Production), true); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /crates/pop-common/src/errors.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{sourcing, templates}; 4 | use thiserror::Error; 5 | 6 | /// Represents the various errors that can occur in the crate. 7 | #[derive(Error, Debug)] 8 | pub enum Error { 9 | /// An error occurred while parsing the provided account address. 10 | #[error("Failed to parse account address: {0}")] 11 | AccountAddressParsing(String), 12 | /// An error occurred. 13 | #[error("Anyhow error: {0}")] 14 | AnyhowError(#[from] anyhow::Error), 15 | /// A configuration error occurred. 16 | #[error("Configuration error: {0}")] 17 | Config(String), 18 | /// A Git error occurred. 19 | #[error("a git error occurred: {0}")] 20 | Git(String), 21 | /// An IO error occurred. 22 | #[error("IO error: {0}")] 23 | IO(#[from] std::io::Error), 24 | /// An error occurred while attempting to create a keypair from the provided URI. 25 | #[error("Failed to create keypair from URI: {0}")] 26 | KeyPairCreation(String), 27 | /// A manifest error occurred. 28 | #[error("Manifest error: {0}")] 29 | ManifestError(#[from] cargo_toml::Error), 30 | /// An error occurred while attempting to retrieve the manifest path. 31 | #[error("Failed to get manifest path: {0}")] 32 | ManifestPath(String), 33 | /// An error occurred during parsing. 34 | #[error("ParseError error: {0}")] 35 | ParseError(#[from] url::ParseError), 36 | /// An error occurred while parsing the provided secret URI. 37 | #[error("Failed to parse secret URI: {0}")] 38 | ParseSecretURI(String), 39 | /// An error occurred during sourcing of a binary. 40 | #[error("SourceError error: {0}")] 41 | SourceError(#[from] sourcing::Error), 42 | /// A template error occurred. 43 | #[error("TemplateError error: {0}")] 44 | TemplateError(#[from] templates::Error), 45 | /// An error occurred while executing a test command. 46 | #[error("Failed to execute test command: {0}")] 47 | TestCommand(String), 48 | /// The command is unsupported. 49 | #[error("Unsupported command: {0}")] 50 | UnsupportedCommand(String), 51 | /// The platform is unsupported. 52 | #[error("Unsupported platform: {arch} {os}")] 53 | UnsupportedPlatform { 54 | /// The architecture of the CPU that is currently in use. 55 | arch: &'static str, 56 | /// The operating system in use. 57 | os: &'static str, 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /crates/pop-common/src/helpers.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::Error; 4 | use std::{ 5 | collections::HashMap, 6 | fs, 7 | io::{Read, Write}, 8 | path::{Component, Path, PathBuf}, 9 | }; 10 | 11 | /// Replaces occurrences of specified strings in a file with new values. 12 | /// 13 | /// # Arguments 14 | /// 15 | /// * `file_path` - A `PathBuf` specifying the path to the file to be modified. 16 | /// * `replacements` - A `HashMap` where each key-value pair represents a target string and its 17 | /// corresponding replacement string. 18 | pub fn replace_in_file(file_path: PathBuf, replacements: HashMap<&str, &str>) -> Result<(), Error> { 19 | // Read the file content 20 | let mut file_content = String::new(); 21 | fs::File::open(&file_path)?.read_to_string(&mut file_content)?; 22 | // Perform the replacements 23 | let mut modified_content = file_content; 24 | for (target, replacement) in &replacements { 25 | modified_content = modified_content.replace(target, replacement); 26 | } 27 | // Write the modified content back to the file 28 | let mut file = fs::File::create(&file_path)?; 29 | file.write_all(modified_content.as_bytes())?; 30 | Ok(()) 31 | } 32 | 33 | /// Gets the last component (name of a project) of a path or returns a default value if the path has 34 | /// no valid last component. 35 | /// 36 | /// # Arguments 37 | /// * `path` - Location path of the project. 38 | /// * `default` - The default string to return if the path has no valid last component. 39 | pub fn get_project_name_from_path<'a>(path: &'a Path, default: &'a str) -> &'a str { 40 | path.file_name().and_then(|name| name.to_str()).unwrap_or(default) 41 | } 42 | 43 | /// Transforms a path without prefix into a relative path starting at the current directory. 44 | /// 45 | /// # Arguments 46 | /// * `path` - The path to be prefixed if needed. 47 | pub fn prefix_with_current_dir_if_needed(path: PathBuf) -> PathBuf { 48 | let components = &path.components().collect::>(); 49 | if !components.is_empty() { 50 | // If the first component is a normal component, we prefix the path with the current dir 51 | if let Component::Normal(_) = components[0] { 52 | return as AsRef>::as_ref(&Component::CurDir).join(path); 53 | } 54 | } 55 | path 56 | } 57 | 58 | /// Returns the relative path from `base` to `full` if `full` is inside `base`. 59 | /// If `full` is outside `base`, returns the absolute path instead. 60 | /// 61 | /// # Arguments 62 | /// * `base` - The base directory to compare against. 63 | /// * `full` - The full path to be shortened. 64 | pub fn get_relative_or_absolute_path(base: &Path, full: &Path) -> PathBuf { 65 | match full.strip_prefix(base) { 66 | Ok(relative) => relative.to_path_buf(), 67 | // If prefix is different, return the full path 68 | Err(_) => full.to_path_buf(), 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | use anyhow::Result; 76 | use std::fs; 77 | 78 | #[test] 79 | fn test_replace_in_file() -> Result<(), Error> { 80 | let temp_dir = tempfile::tempdir()?; 81 | let file_path = temp_dir.path().join("file.toml"); 82 | let mut file = fs::File::create(temp_dir.path().join("file.toml"))?; 83 | writeln!(file, "name = test, version = 5.0.0")?; 84 | let mut replacements_in_cargo = HashMap::new(); 85 | replacements_in_cargo.insert("test", "changed_name"); 86 | replacements_in_cargo.insert("5.0.0", "5.0.1"); 87 | replace_in_file(file_path.clone(), replacements_in_cargo)?; 88 | let content = fs::read_to_string(file_path).expect("Could not read file"); 89 | assert_eq!(content.trim(), "name = changed_name, version = 5.0.1"); 90 | Ok(()) 91 | } 92 | 93 | #[test] 94 | fn get_project_name_from_path_works() -> Result<(), Error> { 95 | let path = Path::new("./path/to/project/my-parachain"); 96 | assert_eq!(get_project_name_from_path(path, "default_name"), "my-parachain"); 97 | Ok(()) 98 | } 99 | 100 | #[test] 101 | fn get_project_name_from_path_default_value() -> Result<(), Error> { 102 | let path = Path::new("./"); 103 | assert_eq!(get_project_name_from_path(path, "my-contract"), "my-contract"); 104 | Ok(()) 105 | } 106 | 107 | #[test] 108 | fn prefix_with_current_dir_if_needed_works_well() { 109 | let no_prefixed_path = PathBuf::from("my/path".to_string()); 110 | let current_dir_prefixed_path = PathBuf::from("./my/path".to_string()); 111 | let parent_dir_prefixed_path = PathBuf::from("../my/path".to_string()); 112 | let root_dir_prefixed_path = PathBuf::from("/my/path".to_string()); 113 | let empty_path = PathBuf::from("".to_string()); 114 | 115 | assert_eq!( 116 | prefix_with_current_dir_if_needed(no_prefixed_path), 117 | PathBuf::from("./my/path/".to_string()) 118 | ); 119 | assert_eq!( 120 | prefix_with_current_dir_if_needed(current_dir_prefixed_path), 121 | PathBuf::from("./my/path/".to_string()) 122 | ); 123 | assert_eq!( 124 | prefix_with_current_dir_if_needed(parent_dir_prefixed_path), 125 | PathBuf::from("../my/path/".to_string()) 126 | ); 127 | assert_eq!( 128 | prefix_with_current_dir_if_needed(root_dir_prefixed_path), 129 | PathBuf::from("/my/path/".to_string()) 130 | ); 131 | assert_eq!(prefix_with_current_dir_if_needed(empty_path), PathBuf::from("".to_string())); 132 | } 133 | 134 | #[test] 135 | fn get_relative_or_absolute_path_works() { 136 | [ 137 | ("/path/to/project", "/path/to/project", ""), 138 | ("/path/to/project", "/path/to/src", "/path/to/src"), 139 | ("/path/to/project", "/path/to/project/main.rs", "main.rs"), 140 | ("/path/to/project", "/path/to/project/../main.rs", "../main.rs"), 141 | ("/path/to/project", "/path/to/project/src/main.rs", "src/main.rs"), 142 | ] 143 | .into_iter() 144 | .for_each(|(base, full, expected)| { 145 | assert_eq!( 146 | get_relative_or_absolute_path(Path::new(base), Path::new(full)), 147 | Path::new(expected) 148 | ); 149 | }); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /crates/pop-common/src/signer.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::errors::Error; 4 | use subxt_signer::{sr25519::Keypair, SecretUri}; 5 | 6 | /// Create a keypair from a secret URI. 7 | /// 8 | /// # Arguments 9 | /// `suri` - Secret URI string used to generate the `Keypair`. 10 | pub fn create_signer(suri: &str) -> Result { 11 | let uri = ::from_str(suri) 12 | .map_err(|e| Error::ParseSecretURI(format!("{}", e)))?; 13 | let keypair = Keypair::from_uri(&uri).map_err(|e| Error::KeyPairCreation(format!("{}", e)))?; 14 | Ok(keypair) 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use super::*; 20 | use anyhow::Result; 21 | 22 | #[test] 23 | fn create_signer_works() -> Result<(), Error> { 24 | let keypair = create_signer("//Alice")?; 25 | assert_eq!( 26 | keypair.public_key().to_account_id().to_string(), 27 | "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" //Alice account 28 | ); 29 | Ok(()) 30 | } 31 | 32 | #[test] 33 | fn create_signer_fails_wrong_key() -> Result<(), Error> { 34 | assert!(matches!(create_signer("11111"), Err(Error::KeyPairCreation(..)))); 35 | Ok(()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/pop-common/src/templates/extractor.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use anyhow::Result; 4 | use std::{fs, io, path::Path}; 5 | 6 | /// Extracts the specified template files from the repository directory to the target directory. 7 | /// 8 | /// # Arguments 9 | /// * `template_name` - The name of the template to extract. 10 | /// * `repo_directory` - The path to the repository directory containing the template. 11 | /// * `target_directory` - The destination path where the template files should be copied. 12 | /// * `ignore_directories` - A vector of directory names to ignore during the extraction. If empty, 13 | /// no directories are ignored. 14 | pub fn extract_template_files( 15 | template_name: &str, 16 | repo_directory: &Path, 17 | target_directory: &Path, 18 | ignore_directories: Option>, 19 | ) -> Result<()> { 20 | let template_directory = repo_directory.join(template_name); 21 | // Recursively copy all directories and files within. Ignores the specified ones. 22 | copy_dir_all(template_directory, target_directory, &ignore_directories.unwrap_or_default())?; 23 | Ok(()) 24 | } 25 | 26 | /// Recursively copy a directory and its files. 27 | /// 28 | /// # Arguments 29 | /// * `src`: - The source path of the directory to be copied. 30 | /// * `dst`: - The destination path where the directory and its contents will be copied. 31 | /// * `ignore_directories` - directories to ignore during the copy process. 32 | fn copy_dir_all( 33 | src: impl AsRef, 34 | dst: impl AsRef, 35 | ignore_directories: &[String], 36 | ) -> io::Result<()> { 37 | fs::create_dir_all(&dst)?; 38 | for entry in fs::read_dir(src)? { 39 | let entry = entry?; 40 | let ty = entry.file_type()?; 41 | if ty.is_dir() && 42 | ignore_directories.contains(&entry.file_name().to_string_lossy().to_string()) 43 | { 44 | continue; 45 | } else if ty.is_dir() { 46 | copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()), ignore_directories)?; 47 | } else { 48 | fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; 49 | } 50 | } 51 | Ok(()) 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | use anyhow::{Error, Result}; 58 | use std::fs; 59 | 60 | fn generate_testing_contract(template: &str) -> Result { 61 | let temp_dir = tempfile::tempdir()?; 62 | let template_directory = temp_dir.path().join(template.to_string()); 63 | fs::create_dir(&template_directory)?; 64 | fs::File::create(&template_directory.join("lib.rs"))?; 65 | fs::File::create(&template_directory.join("Cargo.toml"))?; 66 | fs::create_dir(&temp_dir.path().join("noise_directory"))?; 67 | fs::create_dir(&template_directory.join("frontend"))?; 68 | Ok(temp_dir) 69 | } 70 | #[test] 71 | fn extract_template_files_works() -> Result<(), Error> { 72 | // Contract 73 | let mut temp_dir = generate_testing_contract("erc20")?; 74 | let mut output_dir = tempfile::tempdir()?; 75 | extract_template_files("erc20", temp_dir.path(), output_dir.path(), None)?; 76 | assert!(output_dir.path().join("lib.rs").exists()); 77 | assert!(output_dir.path().join("Cargo.toml").exists()); 78 | assert!(output_dir.path().join("frontend").exists()); 79 | assert!(!output_dir.path().join("noise_directory").exists()); 80 | // ignore the frontend directory 81 | temp_dir = generate_testing_contract("erc721")?; 82 | output_dir = tempfile::tempdir()?; 83 | extract_template_files( 84 | "erc721", 85 | temp_dir.path(), 86 | output_dir.path(), 87 | Some(vec!["frontend".to_string()]), 88 | )?; 89 | assert!(output_dir.path().join("lib.rs").exists()); 90 | assert!(output_dir.path().join("Cargo.toml").exists()); 91 | assert!(!output_dir.path().join("frontend").exists()); 92 | assert!(!output_dir.path().join("noise_directory").exists()); 93 | 94 | Ok(()) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /crates/pop-common/src/templates/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use strum::{EnumMessage, EnumProperty, VariantArray}; 4 | pub use thiserror::Error; 5 | 6 | /// Functions for extracting a template's files. 7 | pub mod extractor; 8 | 9 | /// An error relating to templates or template variants. 10 | #[derive(Error, Debug)] 11 | pub enum Error { 12 | /// The `Repository` property is missing from the template variant. 13 | #[error("The `Repository` property is missing from the template variant")] 14 | RepositoryMissing, 15 | /// The `TypeMissing` property is missing from the template variant. 16 | #[error("The `TypeMissing` property is missing from the template variant")] 17 | TypeMissing, 18 | } 19 | 20 | /// A trait for templates. A template is a variant of a template type. 21 | pub trait Template: 22 | Clone + Default + EnumMessage + EnumProperty + Eq + PartialEq + VariantArray 23 | { 24 | /// The template's type property identifier. 25 | const PROPERTY: &'static str = "Type"; 26 | 27 | /// Get the template's name. 28 | fn name(&self) -> &str { 29 | self.get_message().unwrap_or_default() 30 | } 31 | 32 | /// Get the description of the template. 33 | fn description(&self) -> &str { 34 | self.get_detailed_message().unwrap_or_default() 35 | } 36 | 37 | /// Get the template's repository url. 38 | fn repository_url(&self) -> Result<&str, Error> { 39 | self.get_str("Repository").ok_or(Error::RepositoryMissing) 40 | } 41 | 42 | /// Get the list of supported templates. 43 | fn templates() -> &'static [Self] { 44 | Self::VARIANTS 45 | } 46 | 47 | /// Get the type of the template. 48 | fn template_type(&self) -> Result<&str, Error> { 49 | self.get_str(Self::PROPERTY).ok_or(Error::TypeMissing) 50 | } 51 | 52 | /// Get whether the template is deprecated. 53 | fn is_deprecated(&self) -> bool { 54 | self.get_str("IsDeprecated") == Some("true") 55 | } 56 | 57 | /// Get the deprecation message for the template 58 | fn deprecated_message(&self) -> &str { 59 | self.get_str("DeprecatedMessage").unwrap_or_default() 60 | } 61 | } 62 | 63 | /// A trait for defining overarching types of specific template variants. 64 | /// A Type has many Template variants. 65 | /// The method `default_template` should be implemented unless 66 | /// no default templates are desired. 67 | pub trait Type: Clone + Default + EnumMessage + Eq + PartialEq + VariantArray { 68 | /// Get the list of types supported. 69 | fn types() -> &'static [Self] { 70 | Self::VARIANTS 71 | } 72 | 73 | /// Get types's name. 74 | fn name(&self) -> &str { 75 | self.get_message().unwrap_or_default() 76 | } 77 | 78 | /// Get the default template of the type. 79 | fn default_template(&self) -> Option { 80 | None 81 | } 82 | 83 | /// Get the type's description. 84 | fn description(&self) -> &str { 85 | self.get_detailed_message().unwrap_or_default() 86 | } 87 | 88 | /// Get the list of templates of the type. 89 | fn templates(&self) -> Vec<&T> { 90 | T::VARIANTS 91 | .iter() 92 | .filter(|t| t.get_str(T::PROPERTY) == Some(self.name()) && !t.is_deprecated()) 93 | .collect() 94 | } 95 | 96 | /// Check the type provides the template. 97 | fn provides(&self, template: &T) -> bool { 98 | // Match explicitly on type name (message) 99 | template.get_str(T::PROPERTY) == Some(self.name()) 100 | } 101 | } 102 | 103 | /// The possible values from the variants of an enum. 104 | #[macro_export] 105 | macro_rules! enum_variants { 106 | ($e: ty) => {{ 107 | PossibleValuesParser::new( 108 | <$e>::VARIANTS 109 | .iter() 110 | .map(|p| PossibleValue::new(p.as_ref())) 111 | .collect::>(), 112 | ) 113 | .try_map(|s| <$e>::from_str(&s).map_err(|e| format!("could not convert from {s} to type"))) 114 | }}; 115 | } 116 | 117 | /// The possible values from the variants of an enum which are not deprecated. 118 | #[macro_export] 119 | macro_rules! enum_variants_without_deprecated { 120 | ($e:ty) => {{ 121 | <$e>::VARIANTS 122 | .iter() 123 | .filter(|variant| !variant.is_deprecated()) // Exclude deprecated variants for --help 124 | .map(|v| v.as_ref()) 125 | .collect::>() 126 | .join(", ") 127 | }}; 128 | } 129 | -------------------------------------------------------------------------------- /crates/pop-common/src/test.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::errors::Error; 4 | use duct::cmd; 5 | use std::path::Path; 6 | 7 | /// Run tests of a Rust project. 8 | /// 9 | /// # Arguments 10 | /// 11 | /// * `path` - location of the project. 12 | pub fn test_project(path: Option<&Path>) -> Result<(), Error> { 13 | // Execute `cargo test` command in the specified directory. 14 | cmd("cargo", vec!["test"]) 15 | .dir(path.unwrap_or_else(|| Path::new("./"))) 16 | .run() 17 | .map_err(|e| Error::TestCommand(format!("Cargo test command failed: {}", e)))?; 18 | Ok(()) 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | use tempfile; 25 | 26 | #[test] 27 | fn test_project_works() -> Result<(), Error> { 28 | let temp_dir = tempfile::tempdir()?; 29 | cmd("cargo", ["new", "test_contract", "--bin"]).dir(temp_dir.path()).run()?; 30 | test_project(Some(&temp_dir.path().join("test_contract")))?; 31 | Ok(()) 32 | } 33 | 34 | #[test] 35 | fn test_project_wrong_directory() -> Result<(), Error> { 36 | let temp_dir = tempfile::tempdir()?; 37 | assert!(matches!( 38 | test_project(Some(&temp_dir.path().join(""))), 39 | Err(Error::TestCommand(..)) 40 | )); 41 | Ok(()) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/pop-contracts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "Library for generating, building, deploying, and calling ink! smart contracts." 3 | documentation = "https://docs.rs/pop-contracts/latest/pop_contracts" 4 | edition.workspace = true 5 | license = "Apache-2.0" 6 | name = "pop-contracts" 7 | readme = "README.md" 8 | repository.workspace = true 9 | version.workspace = true 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | anyhow.workspace = true 15 | duct.workspace = true 16 | reqwest.workspace = true 17 | tempfile.workspace = true 18 | thiserror.workspace = true 19 | tokio.workspace = true 20 | url.workspace = true 21 | 22 | heck.workspace = true 23 | ink_env = { workspace = true, optional = true } 24 | ink_env_v6 = { workspace = true, optional = true } 25 | sp-core = { workspace = true, optional = true } 26 | sp-core_inkv6 = { workspace = true, optional = true } 27 | sp-weights.workspace = true 28 | strum.workspace = true 29 | strum_macros.workspace = true 30 | subxt-signer.workspace = true 31 | subxt.workspace = true 32 | 33 | # cargo-contracts 34 | contract-build = { workspace = true, optional = true } 35 | contract-build_inkv6 = { workspace = true, optional = true } 36 | contract-extrinsics = { workspace = true, optional = true } 37 | contract-extrinsics_inkv6 = { workspace = true, optional = true } 38 | contract-transcode = { workspace = true, optional = true } 39 | contract-transcode_inkv6 = { workspace = true, optional = true } 40 | scale-info = { workspace = true } 41 | 42 | # pop 43 | pop-common = { path = "../pop-common", version = "0.8.1" } 44 | 45 | [dev-dependencies] 46 | # Used in doc tests. 47 | tokio-test.workspace = true 48 | 49 | [features] 50 | default = ["v5"] 51 | v5 = ["dep:contract-build", "dep:contract-extrinsics", "dep:contract-transcode", "dep:ink_env", "dep:sp-core"] 52 | v6 = ["dep:contract-build_inkv6", "dep:contract-extrinsics_inkv6", "dep:contract-transcode_inkv6", "dep:ink_env_v6", "dep:sp-core_inkv6"] -------------------------------------------------------------------------------- /crates/pop-contracts/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /crates/pop-contracts/src/build.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{errors::Error, utils::get_manifest_path}; 4 | pub use contract_build::Verbosity; 5 | use contract_build::{execute, BuildMode, BuildResult, ExecuteArgs}; 6 | #[cfg(feature = "v6")] 7 | use contract_build_inkv6 as contract_build; 8 | use std::path::Path; 9 | 10 | /// Build the smart contract located at the specified `path` in `build_release` mode. 11 | /// 12 | /// # Arguments 13 | /// * `path` - The optional path to the smart contract manifest, defaulting to the current directory 14 | /// if not specified. 15 | /// * `release` - Whether the smart contract should be built without any debugging functionality. 16 | /// * `verbosity` - The build output verbosity. 17 | pub fn build_smart_contract( 18 | path: Option<&Path>, 19 | release: bool, 20 | verbosity: Verbosity, 21 | ) -> anyhow::Result { 22 | let manifest_path = get_manifest_path(path)?; 23 | 24 | let build_mode = match release { 25 | true => BuildMode::Release, 26 | false => BuildMode::Debug, 27 | }; 28 | 29 | // Default values 30 | let args = ExecuteArgs { manifest_path, build_mode, verbosity, ..Default::default() }; 31 | 32 | // Execute the build and log the output of the build 33 | execute(args) 34 | } 35 | 36 | /// Determines whether the manifest at the supplied path is a supported smart contract project. 37 | /// 38 | /// # Arguments 39 | /// * `path` - The optional path to the manifest, defaulting to the current directory if not 40 | /// specified. 41 | pub fn is_supported(path: Option<&Path>) -> Result { 42 | Ok(pop_common::manifest::from_path(path)?.dependencies.contains_key("ink")) 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | use contract_build::new_contract_project; 49 | use duct::cmd; 50 | 51 | #[test] 52 | fn is_supported_works() -> anyhow::Result<()> { 53 | let temp_dir = tempfile::tempdir()?; 54 | let path = temp_dir.path(); 55 | 56 | // Standard rust project 57 | let name = "hello_world"; 58 | cmd("cargo", ["new", name]).dir(&path).run()?; 59 | assert!(!is_supported(Some(&path.join(name)))?); 60 | 61 | // Contract 62 | let name = "flipper"; 63 | new_contract_project(name, Some(&path))?; 64 | assert!(is_supported(Some(&path.join(name)))?); 65 | Ok(()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /crates/pop-contracts/src/errors.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use pop_common::sourcing::Error as SourcingError; 4 | use thiserror::Error; 5 | 6 | /// Represents the various errors that can occur in the crate. 7 | #[derive(Error, Debug)] 8 | #[allow(clippy::enum_variant_names)] 9 | pub enum Error { 10 | #[error("Anyhow error: {0}")] 11 | AnyhowError(#[from] anyhow::Error), 12 | #[error("Failed to parse balance: {0}")] 13 | BalanceParsing(String), 14 | #[error("{0}")] 15 | CallContractError(String), 16 | #[error("{0}")] 17 | CommonError(#[from] pop_common::Error), 18 | #[error("Pre-submission dry-run failed: {0}")] 19 | DryRunUploadContractError(String), 20 | #[error("Pre-submission dry-run failed: {0}")] 21 | DryRunCallContractError(String), 22 | #[error("Failed to parse hex encoded bytes: {0}")] 23 | HexParsing(String), 24 | #[error("HTTP error: {0}")] 25 | HttpError(#[from] reqwest::Error), 26 | #[error("Incorrect number of arguments provided. Expecting {expected}, {provided} provided")] 27 | IncorrectArguments { expected: usize, provided: usize }, 28 | #[error("Failed to install {0}")] 29 | InstallContractsNode(String), 30 | #[error("{0}")] 31 | InstantiateContractError(String), 32 | #[error("Invalid constructor name: {0}")] 33 | InvalidConstructorName(String), 34 | #[error("Invalid message name: {0}")] 35 | InvalidMessageName(String), 36 | #[error("Invalid name: {0}")] 37 | InvalidName(String), 38 | #[error("IO error: {0}")] 39 | IO(#[from] std::io::Error), 40 | #[error("Failed to get manifest path: {0}")] 41 | ManifestPath(String), 42 | /// Error returned when mapping an account fails. 43 | #[error("Failed to map account: {0}")] 44 | MapAccountError(String), 45 | #[error("Argument {0} is required")] 46 | MissingArgument(String), 47 | #[error("Failed to create new contract project: {0}")] 48 | NewContract(String), 49 | #[error("ParseError error: {0}")] 50 | ParseError(#[from] url::ParseError), 51 | #[error("The `Repository` property is missing from the template variant")] 52 | RepositoryMissing, 53 | #[error("Sourcing error {0}")] 54 | SourcingError(SourcingError), 55 | #[error("Failed to execute test command: {0}")] 56 | TestCommand(String), 57 | #[error("Unsupported platform: {os}")] 58 | UnsupportedPlatform { os: &'static str }, 59 | #[error("{0}")] 60 | UploadContractError(String), 61 | } 62 | -------------------------------------------------------------------------------- /crates/pop-contracts/src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | #![doc = include_str!("../README.md")] 4 | #![cfg(any(feature = "v5", feature = "v6"))] 5 | 6 | #[cfg(all(feature = "v5", feature = "v6"))] 7 | compile_error!("only feature \"v5\" OR \"v6\" must be enabled"); 8 | 9 | mod build; 10 | mod call; 11 | mod errors; 12 | mod new; 13 | mod node; 14 | mod templates; 15 | mod test; 16 | mod testing; 17 | mod up; 18 | mod utils; 19 | 20 | pub use build::{build_smart_contract, is_supported, Verbosity}; 21 | pub use call::{ 22 | call_smart_contract, call_smart_contract_from_signed_payload, dry_run_call, 23 | dry_run_gas_estimate_call, get_call_payload, set_up_call, CallOpts, 24 | }; 25 | pub use new::{create_smart_contract, is_valid_contract_name}; 26 | pub use node::{contracts_node_generator, is_chain_alive, run_contracts_node}; 27 | pub use templates::{Contract, ContractType}; 28 | pub use test::test_e2e_smart_contract; 29 | pub use testing::{mock_build_process, new_environment}; 30 | pub use up::{ 31 | dry_run_gas_estimate_instantiate, dry_run_upload, get_contract_code, 32 | instantiate_contract_signed, instantiate_smart_contract, set_up_deployment, set_up_upload, 33 | submit_signed_payload, upload_contract_signed, upload_smart_contract, ContractInfo, UpOpts, 34 | }; 35 | pub use utils::{ 36 | metadata::{get_message, get_messages, ContractFunction}, 37 | parse_hex_bytes, 38 | }; 39 | // External exports 40 | pub use sp_weights::Weight; 41 | #[cfg(feature = "v5")] 42 | pub use { 43 | contract_extrinsics::{extrinsic_calls::UploadCode, CallExec}, 44 | ink_env::{DefaultEnvironment, Environment}, 45 | sp_core::Bytes, 46 | up::{get_code_hash_from_event, get_instantiate_payload, get_upload_payload}, 47 | }; 48 | #[cfg(feature = "v6")] 49 | pub use { 50 | contract_extrinsics_inkv6::{CallExec, ExtrinsicOpts, UploadCode}, 51 | ink_env_v6::{DefaultEnvironment, Environment}, 52 | sp_core_inkv6::Bytes, 53 | up::{get_instantiate_payload, get_upload_payload}, 54 | utils::map_account::AccountMapper, 55 | }; 56 | -------------------------------------------------------------------------------- /crates/pop-contracts/src/test.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::errors::Error; 4 | use duct::cmd; 5 | use std::{env, path::Path}; 6 | 7 | /// Run e2e tests of a smart contract. 8 | /// 9 | /// # Arguments 10 | /// 11 | /// * `path` - location of the smart contract. 12 | /// * `node` - location of the contracts node binary. 13 | pub fn test_e2e_smart_contract(path: Option<&Path>, node: Option<&Path>) -> Result<(), Error> { 14 | // Set the environment variable `CONTRACTS_NODE` to the path of the contracts node. 15 | if let Some(node) = node { 16 | env::set_var("CONTRACTS_NODE", node); 17 | } 18 | // Execute `cargo test --features=e2e-tests` command in the specified directory. 19 | cmd("cargo", vec!["test", "--features=e2e-tests"]) 20 | .dir(path.unwrap_or_else(|| Path::new("./"))) 21 | .run() 22 | .map_err(|e| Error::TestCommand(format!("Cargo test command failed: {}", e)))?; 23 | Ok(()) 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | use tempfile; 30 | 31 | #[test] 32 | fn test_e2e_smart_contract_set_env_variable() -> Result<(), Error> { 33 | let temp_dir = tempfile::tempdir()?; 34 | cmd("cargo", ["new", "test_contract", "--bin"]).dir(temp_dir.path()).run()?; 35 | // Ignore 2e2 testing in this scenario, will fail. Only test if the environment variable 36 | // CONTRACTS_NODE is set. 37 | let err = test_e2e_smart_contract(Some(&temp_dir.path().join("test_contract")), None); 38 | assert!(err.is_err()); 39 | // The environment variable `CONTRACTS_NODE` should not be set. 40 | assert!(env::var("CONTRACTS_NODE").is_err()); 41 | let err = test_e2e_smart_contract( 42 | Some(&temp_dir.path().join("test_contract")), 43 | Some(&Path::new("/path/to/contracts-node")), 44 | ); 45 | assert!(err.is_err()); 46 | // The environment variable `CONTRACTS_NODE` should has been set. 47 | assert_eq!( 48 | env::var("CONTRACTS_NODE").unwrap(), 49 | Path::new("/path/to/contracts-node").display().to_string() 50 | ); 51 | Ok(()) 52 | } 53 | 54 | #[test] 55 | fn test_e2e_smart_contract_fails_no_e2e_tests() -> Result<(), Error> { 56 | let temp_dir = tempfile::tempdir()?; 57 | cmd("cargo", ["new", "test_contract", "--bin"]).dir(temp_dir.path()).run()?; 58 | assert!(matches!( 59 | test_e2e_smart_contract(Some(&temp_dir.path().join("test_contract")), None), 60 | Err(Error::TestCommand(..)) 61 | )); 62 | Ok(()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/pop-contracts/src/testing.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{create_smart_contract, Contract}; 4 | use anyhow::Result; 5 | use std::{ 6 | fs::{copy, create_dir}, 7 | path::Path, 8 | }; 9 | 10 | /// Generates a smart contract test environment. 11 | /// 12 | /// * `name` - The name of the contract to be created. 13 | pub fn new_environment(name: &str) -> Result { 14 | let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); 15 | let temp_contract_dir = temp_dir.path().join(name); 16 | create_dir(&temp_contract_dir)?; 17 | create_smart_contract(name, temp_contract_dir.as_path(), &Contract::Standard)?; 18 | Ok(temp_dir) 19 | } 20 | 21 | /// Mocks the build process by generating contract artifacts in a specified temporary directory. 22 | /// 23 | /// * `temp_contract_dir` - The root directory where the `target` folder and artifacts will be 24 | /// created. 25 | /// * `contract_file` - The path to the mocked contract file to be copied. 26 | /// * `metadata_file` - The path to the mocked metadata file to be copied. 27 | pub fn mock_build_process

(temp_contract_dir: P, contract_file: P, metadata_file: P) -> Result<()> 28 | where 29 | P: AsRef, 30 | { 31 | // Create a target directory 32 | let target_contract_dir = temp_contract_dir.as_ref().join("target"); 33 | create_dir(&target_contract_dir)?; 34 | create_dir(target_contract_dir.join("ink"))?; 35 | // Copy a mocked testing.contract and testing.json files inside the target directory 36 | copy(contract_file, target_contract_dir.join("ink/testing.contract"))?; 37 | copy(metadata_file, target_contract_dir.join("ink/testing.json"))?; 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /crates/pop-contracts/src/utils/map_account.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{errors::Error, DefaultEnvironment}; 4 | use contract_extrinsics_inkv6::{ExtrinsicOpts, MapAccountCommandBuilder, MapAccountExec}; 5 | use pop_common::{DefaultConfig, Keypair}; 6 | use subxt::{ext::scale_encode::EncodeAsType, utils::H160}; 7 | 8 | /// A helper struct for performing account mapping operations. 9 | pub struct AccountMapper { 10 | map_exec: MapAccountExec, 11 | } 12 | 13 | impl AccountMapper { 14 | /// Creates a new `AccountMapper` instance. 15 | /// 16 | /// # Arguments 17 | /// * `extrinsic_opts` - Options used to build and submit a contract extrinsic. 18 | pub async fn new( 19 | extrinsic_opts: &ExtrinsicOpts, 20 | ) -> Result { 21 | let map_exec = MapAccountCommandBuilder::new(extrinsic_opts.clone()).done().await?; 22 | Ok(Self { map_exec }) 23 | } 24 | 25 | /// Checks whether the account needs to be mapped by performing a dry run. 26 | pub async fn needs_mapping(&self) -> Result { 27 | Ok(self.map_exec.map_account_dry_run().await.is_ok()) 28 | } 29 | 30 | /// Performs the actual account mapping. 31 | pub async fn map_account(&self) -> Result { 32 | let result = self 33 | .map_exec 34 | .map_account() 35 | .await 36 | .map_err(|e| Error::MapAccountError(e.to_string()))?; 37 | Ok(result.address) 38 | } 39 | } 40 | 41 | // Create a call to `Revive::map_account`. 42 | #[derive(Debug, EncodeAsType)] 43 | #[encode_as_type(crate_path = "subxt::ext::scale_encode")] 44 | pub(crate) struct MapAccount {} 45 | 46 | impl MapAccount { 47 | // Construct an empty `MapAccount` payload. 48 | pub(crate) fn new() -> Self { 49 | Self {} 50 | } 51 | // Create a call to `Revive::map_account` with no arguments. 52 | pub(crate) fn build(self) -> subxt::tx::DefaultPayload { 53 | subxt::tx::DefaultPayload::new("Revive", "map_account", self) 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | use crate::{ 61 | contracts_node_generator, mock_build_process, new_environment, run_contracts_node, 62 | }; 63 | use anyhow::Result; 64 | use contract_extrinsics_inkv6::ExtrinsicOptsBuilder; 65 | use pop_common::{find_free_port, parse_h160_account, set_executable_permission}; 66 | use std::{env, process::Command}; 67 | use subxt_signer::sr25519::dev; 68 | use url::Url; 69 | 70 | #[tokio::test] 71 | async fn map_account_works() -> Result<()> { 72 | let random_port = find_free_port(None); 73 | let localhost_url = format!("ws://127.0.0.1:{}", random_port); 74 | let temp_dir = new_environment("testing")?; 75 | let current_dir = env::current_dir().expect("Failed to get current directory"); 76 | mock_build_process( 77 | temp_dir.path().join("testing"), 78 | current_dir.join("./tests/files/testing.contract"), 79 | current_dir.join("./tests/files/testing.json"), 80 | )?; 81 | // Run the process contracts-node for the test. 82 | let cache = temp_dir.path().join(""); 83 | let binary = contracts_node_generator(cache, None).await?; 84 | binary.source(false, &(), true).await?; 85 | set_executable_permission(binary.path())?; 86 | let process = run_contracts_node(binary.path(), None, random_port).await?; 87 | // Alice is mapped when running the contracts-node. 88 | let signer = dev::bob(); 89 | let extrinsic_opts: ExtrinsicOpts = 90 | ExtrinsicOptsBuilder::new(signer) 91 | .file(Some(current_dir.join("./tests/files/testing.contract"))) 92 | .url(Url::parse(&localhost_url)?) 93 | .done(); 94 | let map = AccountMapper::new(&extrinsic_opts).await?; 95 | assert!(map.needs_mapping().await?); 96 | 97 | let address = map.map_account().await?; 98 | assert_eq!(address, parse_h160_account("0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01")?); 99 | 100 | assert!(!map.needs_mapping().await?); 101 | 102 | // Stop the process contracts-node after test. 103 | Command::new("kill") 104 | .args(["-s", "TERM", &process.id().to_string()]) 105 | .spawn()? 106 | .wait()?; 107 | Ok(()) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /crates/pop-contracts/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{errors::Error, Bytes, DefaultEnvironment, Environment}; 4 | use std::{ 5 | path::{Path, PathBuf}, 6 | str::FromStr, 7 | }; 8 | #[cfg(feature = "v5")] 9 | use { 10 | contract_build::{util::decode_hex, ManifestPath}, 11 | contract_extrinsics::BalanceVariant, 12 | }; 13 | #[cfg(feature = "v6")] 14 | use { 15 | contract_build_inkv6::{util::decode_hex, ManifestPath}, 16 | contract_extrinsics_inkv6::BalanceVariant, 17 | }; 18 | 19 | /// Module for account mapping functionality. 20 | #[cfg(feature = "v6")] 21 | pub mod map_account; 22 | pub mod metadata; 23 | 24 | /// Retrieves the manifest path for a contract project. 25 | /// 26 | /// # Arguments 27 | /// * `path` - An optional path to the project directory. 28 | pub fn get_manifest_path(path: Option<&Path>) -> Result { 29 | if let Some(path) = path { 30 | let full_path = PathBuf::from(path.to_string_lossy().to_string() + "/Cargo.toml"); 31 | ManifestPath::try_from(Some(full_path)) 32 | .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) 33 | } else { 34 | ManifestPath::try_from(path.as_ref()) 35 | .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {}", e))) 36 | } 37 | } 38 | 39 | /// Parses a balance value from a string representation. 40 | /// 41 | /// # Arguments 42 | /// * `balance` - A string representing the balance value to parse. 43 | pub fn parse_balance( 44 | balance: &str, 45 | ) -> Result::Balance>, Error> { 46 | BalanceVariant::from_str(balance).map_err(|e| Error::BalanceParsing(format!("{}", e))) 47 | } 48 | 49 | /// Parse hex encoded bytes. 50 | /// 51 | /// # Arguments 52 | /// * `input` - A string containing hex-encoded bytes. 53 | pub fn parse_hex_bytes(input: &str) -> Result { 54 | let bytes = decode_hex(input).map_err(|e| Error::HexParsing(format!("{}", e)))?; 55 | Ok(bytes.into()) 56 | } 57 | 58 | /// Canonicalizes the given path to ensure consistency and resolve any symbolic links. 59 | /// 60 | /// # Arguments 61 | /// * `target` - A reference to the `Path` to be canonicalized. 62 | pub fn canonicalized_path(target: &Path) -> Result { 63 | // Canonicalize the target path to ensure consistency and resolve any symbolic links. 64 | target 65 | .canonicalize() 66 | // If an I/O error occurs during canonicalization, convert it into an Error enum variant. 67 | .map_err(Error::IO) 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | use anyhow::Result; 74 | use std::fs; 75 | 76 | fn setup_test_environment() -> Result { 77 | let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); 78 | let temp_contract_dir = temp_dir.path().join("test_contract"); 79 | fs::create_dir(&temp_contract_dir)?; 80 | crate::create_smart_contract( 81 | "test_contract", 82 | temp_contract_dir.as_path(), 83 | &crate::Contract::Standard, 84 | )?; 85 | Ok(temp_dir) 86 | } 87 | 88 | #[test] 89 | fn test_get_manifest_path() -> Result<(), Error> { 90 | let temp_dir = setup_test_environment()?; 91 | get_manifest_path(Some(&PathBuf::from(temp_dir.path().join("test_contract"))))?; 92 | Ok(()) 93 | } 94 | 95 | #[test] 96 | fn test_canonicalized_path() -> Result<(), Error> { 97 | let temp_dir = tempfile::tempdir()?; 98 | // Error case 99 | let error_directory = canonicalized_path(&temp_dir.path().join("my_directory")); 100 | assert!(error_directory.is_err()); 101 | // Success case 102 | canonicalized_path(temp_dir.path())?; 103 | Ok(()) 104 | } 105 | 106 | #[test] 107 | fn parse_balance_works() -> Result<(), Error> { 108 | let balance = parse_balance("100000")?; 109 | assert_eq!(balance, BalanceVariant::Default(100000)); 110 | Ok(()) 111 | } 112 | 113 | #[test] 114 | fn parse_balance_fails_wrong_balance() -> Result<(), Error> { 115 | assert!(matches!(parse_balance("wrongbalance"), Err(super::Error::BalanceParsing(..)))); 116 | Ok(()) 117 | } 118 | 119 | #[test] 120 | fn parse_hex_bytes_works() -> Result<(), Error> { 121 | let input_in_hex = "48656c6c6f"; 122 | let result = parse_hex_bytes(input_in_hex)?; 123 | assert_eq!(result, Bytes(vec![72, 101, 108, 108, 111])); 124 | Ok(()) 125 | } 126 | 127 | #[test] 128 | fn parse_hex_bytes_fails_wrong_input() -> Result<()> { 129 | assert!(matches!(parse_hex_bytes("wronghexvalue"), Err(Error::HexParsing(..)))); 130 | Ok(()) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /crates/pop-contracts/tests/files/testing.polkavm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r0gue-io/pop-cli/92b9d02f8c1ce78f3d2b47b77f1b15d09a2fe6c8/crates/pop-contracts/tests/files/testing.polkavm -------------------------------------------------------------------------------- /crates/pop-contracts/tests/files/testing.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r0gue-io/pop-cli/92b9d02f8c1ce78f3d2b47b77f1b15d09a2fe6c8/crates/pop-contracts/tests/files/testing.wasm -------------------------------------------------------------------------------- /crates/pop-parachains/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pop-parachains" 3 | description = "Library for generating, building and running parachains." 4 | license = "Apache-2.0" 5 | documentation = "https://docs.rs/pop-parachains/latest/pop_parachains" 6 | edition.workspace = true 7 | readme = "README.md" 8 | repository.workspace = true 9 | version.workspace = true 10 | 11 | [dependencies] 12 | anyhow.workspace = true 13 | clap.workspace = true 14 | duct.workspace = true 15 | glob.workspace = true 16 | serde_json.workspace = true 17 | strum.workspace = true 18 | strum_macros.workspace = true 19 | subxt-signer.workspace = true 20 | subxt.workspace = true 21 | tempfile.workspace = true 22 | thiserror.workspace = true 23 | tokio.workspace = true 24 | url.workspace = true 25 | 26 | askama.workspace = true 27 | indexmap.workspace = true 28 | scale.workspace = true 29 | scale-info.workspace = true 30 | scale-value.workspace = true 31 | sp-core.workspace = true 32 | srtool-lib.workspace = true 33 | symlink.workspace = true 34 | toml_edit.workspace = true 35 | walkdir.workspace = true 36 | # Zombienet 37 | zombienet-configuration.workspace = true 38 | zombienet-sdk.workspace = true 39 | 40 | # Benchmarking 41 | cumulus-primitives-proof-size-hostfunction.workspace = true 42 | frame-benchmarking-cli.workspace = true 43 | sc-chain-spec.workspace = true 44 | serde.workspace = true 45 | sp-runtime.workspace = true 46 | sp-statement-store.workspace = true 47 | 48 | # Try Runtime 49 | frame-try-runtime = { workspace = true, features = ["try-runtime"] } 50 | sc-cli.workspace = true 51 | sp-version.workspace = true 52 | 53 | # Pop 54 | pop-common = { path = "../pop-common", version = "0.8.1" } 55 | 56 | [dev-dependencies] 57 | # Used in doc tests. 58 | tokio-test.workspace = true 59 | -------------------------------------------------------------------------------- /crates/pop-parachains/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /crates/pop-parachains/askama.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | # Directories to search for templates, relative to the crate root. 3 | dirs = ["templates"] 4 | # Unless you add a `-` in a block, whitespace characters won't be trimmed. 5 | whitespace = "preserve" 6 | default_syntax = "substrate" 7 | 8 | [[syntax]] 9 | name = "substrate" 10 | block_start = "{{" 11 | block_end = "}}" 12 | comment_start = "#{" 13 | comment_end = "}#" 14 | expr_start = "^^" 15 | expr_end = "^^" 16 | 17 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/accounts.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use scale::Encode; 4 | use sp_core::crypto::AccountId32; 5 | #[cfg(test)] 6 | use sp_core::crypto::Ss58Codec; 7 | 8 | /// Calculate the sovereign account of a sibling rollup - i.e., from the context of a rollup. 9 | /// 10 | /// # Arguments 11 | /// * `id` - The rollup identifier. 12 | pub(crate) fn sibl(id: u32) -> AccountId32 { 13 | sovereign_account(id, b"sibl") 14 | } 15 | 16 | /// Calculate the sovereign account of a child rollup - i.e., from the context of a relay chain. 17 | /// 18 | /// # Arguments 19 | /// * `id` - The rollup identifier. 20 | #[allow(dead_code)] 21 | pub(crate) fn para(id: u32) -> AccountId32 { 22 | sovereign_account(id, b"para") 23 | } 24 | 25 | /// Calculate the sovereign account of a rollup. 26 | /// 27 | /// # Arguments 28 | /// * `id` - The rollup identifier. 29 | fn sovereign_account(id: u32, prefix: &[u8; 4]) -> AccountId32 { 30 | let mut account = [0u8; 32]; 31 | account[..4].copy_from_slice(prefix); 32 | let mut x = &mut account[4..8]; 33 | id.encode_to(&mut x); 34 | account.into() 35 | } 36 | 37 | #[test] 38 | fn sibling_rollup_sovereign_account_works() { 39 | let account = sibl(4_001); 40 | assert_eq!(account.to_ss58check(), "5Eg2fnt8cGL5CBhRRhi59abAwb3SPoAdPJpN9qY7bQqpzpf6"); 41 | } 42 | 43 | #[test] 44 | fn child_rollup_sovereign_account_works() { 45 | let account = para(4_001); 46 | assert_eq!(account.to_ss58check(), "5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ"); 47 | } 48 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/bench/binary.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use pop_common::{ 4 | git::GitHub, 5 | polkadot_sdk::sort_by_latest_stable_version, 6 | sourcing::{ 7 | filters::prefix, 8 | traits::{ 9 | enums::{Source as _, *}, 10 | Source as SourceT, 11 | }, 12 | ArchiveFileSpec, Binary, 13 | GitHub::*, 14 | Source, 15 | }, 16 | target, Error, 17 | }; 18 | use std::path::PathBuf; 19 | use strum_macros::EnumProperty; 20 | 21 | #[derive(Debug, EnumProperty, PartialEq)] 22 | pub(super) enum BenchmarkingCli { 23 | #[strum(props( 24 | Repository = "https://github.com/r0gue-io/polkadot", 25 | Binary = "frame-omni-bencher", 26 | TagPattern = "polkadot-{version}", 27 | Fallback = "stable2412" 28 | ))] 29 | OmniBencher, 30 | } 31 | 32 | impl SourceT for BenchmarkingCli { 33 | type Error = Error; 34 | /// Defines the source of the binary required for benchmarking. 35 | fn source(&self) -> Result { 36 | // Source from GitHub release asset 37 | let repo = GitHub::parse(self.repository())?; 38 | let binary = self.binary(); 39 | Ok(Source::GitHub(ReleaseArchive { 40 | owner: repo.org, 41 | repository: repo.name, 42 | tag: None, 43 | tag_pattern: self.tag_pattern().map(|t| t.into()), 44 | prerelease: false, 45 | version_comparator: sort_by_latest_stable_version, 46 | fallback: self.fallback().into(), 47 | archive: format!("{binary}-{}.tar.gz", target()?), 48 | contents: vec![ArchiveFileSpec::new(binary.into(), Some(binary.into()), true)], 49 | latest: None, 50 | })) 51 | } 52 | } 53 | 54 | /// Generate the source of the `frame-omni-bencher` binary on the remote repository. 55 | /// 56 | /// # Arguments 57 | /// * `cache` - The path to the directory where the binary should be cached. 58 | /// * `version` - An optional version string. If `None`, the latest available version is used. 59 | pub async fn omni_bencher_generator( 60 | cache: PathBuf, 61 | version: Option<&str>, 62 | ) -> Result { 63 | let cli = BenchmarkingCli::OmniBencher; 64 | let name = cli.binary().to_string(); 65 | let source = cli 66 | .source()? 67 | .resolve(&name, version, cache.as_path(), |f| prefix(f, &name)) 68 | .await 69 | .into(); 70 | let binary = Binary::Source { name, source, cache: cache.to_path_buf() }; 71 | Ok(binary) 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | use tempfile::tempdir; 78 | 79 | #[tokio::test] 80 | async fn omni_bencher_generator_works() -> Result<(), Error> { 81 | let temp_dir = tempdir()?; 82 | let temp_dir_path = temp_dir.into_path(); 83 | let version = "polkadot-stable2412-4"; 84 | let binary = omni_bencher_generator(temp_dir_path.clone(), Some(version)).await?; 85 | assert!(matches!(binary, Binary::Source { name: _, source, cache } 86 | if source == Source::GitHub(ReleaseArchive { 87 | owner: "r0gue-io".to_string(), 88 | repository: "polkadot".to_string(), 89 | tag: Some(version.to_string()), 90 | tag_pattern: Some("polkadot-{version}".into()), 91 | prerelease: false, 92 | version_comparator: sort_by_latest_stable_version, 93 | fallback: "stable2412".to_string(), 94 | archive: format!("frame-omni-bencher-{}.tar.gz", target()?), 95 | contents: ["frame-omni-bencher"].map(|b| ArchiveFileSpec::new(b.into(), Some(b.into()), true)).to_vec(), 96 | latest: binary.latest().map(|l| l.to_string()), 97 | }).into() && 98 | cache == temp_dir_path.as_path() 99 | )); 100 | Ok(()) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/generator/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pub mod pallet; 4 | pub mod parachain; 5 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/generator/parachain.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use askama::Template; 4 | 5 | #[derive(Template)] 6 | #[template(path = "base/chain_spec.templ", escape = "none")] 7 | pub(crate) struct ChainSpec { 8 | pub(crate) token_symbol: String, 9 | pub(crate) decimals: u8, 10 | pub(crate) initial_endowment: String, 11 | // Identifies the template used to generate the chain. 12 | pub(crate) based_on: String, 13 | } 14 | 15 | #[derive(Template)] 16 | #[template(path = "base/network.templ", escape = "none")] 17 | pub(crate) struct Network { 18 | pub(crate) node: String, 19 | } 20 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | #![doc = include_str!("../README.md")] 4 | 5 | mod accounts; 6 | /// Provides functionality for benchmarking. 7 | pub mod bench; 8 | mod build; 9 | /// Provides functionality to construct, encode, sign, and submit chain extrinsics. 10 | mod call; 11 | /// Deployment providers' metadata and utility functions. 12 | mod deployer_providers; 13 | mod errors; 14 | mod generator; 15 | mod new_pallet; 16 | mod new_parachain; 17 | /// A registry of parachains. 18 | pub mod registry; 19 | mod relay; 20 | mod templates; 21 | mod traits; 22 | /// Provides functionality for testing runtime upgrades. 23 | pub mod try_runtime; 24 | /// Provides functionality for launching a local network. 25 | pub mod up; 26 | mod utils; 27 | 28 | pub use bench::{ 29 | binary::*, generate_binary_benchmarks, generate_omni_bencher_benchmarks, 30 | generate_pallet_benchmarks, get_preset_names, get_runtime_path, load_pallet_extrinsics, 31 | BenchmarkingCliCommand, GenesisBuilderPolicy, PalletExtrinsicsRegistry, 32 | GENESIS_BUILDER_DEV_PRESET, 33 | }; 34 | pub use build::{ 35 | binary_path, build_parachain, build_project, export_wasm_file, generate_genesis_state_file, 36 | generate_plain_chain_spec, generate_raw_chain_spec, is_supported, runtime, 37 | runtime::{ContainerEngine, DeterministicBuilder}, 38 | runtime_binary_path, ChainSpec, 39 | }; 40 | pub use call::{ 41 | construct_extrinsic, construct_proxy_extrinsic, construct_sudo_extrinsic, decode_call_data, 42 | encode_call_data, 43 | metadata::{ 44 | action::{supported_actions, Action}, 45 | find_dispatchable_by_name, find_pallet_by_name, 46 | params::Param, 47 | parse_chain_metadata, Function, Pallet, 48 | }, 49 | parse_and_format_events, set_up_client, sign_and_submit_extrinsic, submit_signed_extrinsic, 50 | CallData, 51 | }; 52 | pub use deployer_providers::{DeploymentProvider, SupportedChains}; 53 | pub use errors::Error; 54 | pub use indexmap::IndexSet; 55 | pub use new_pallet::{create_pallet_template, new_pallet_options::*, TemplatePalletConfig}; 56 | pub use new_parachain::instantiate_template_dir; 57 | pub use relay::{clear_dmpq, RelayChain, Reserved}; 58 | pub use try_runtime::{ 59 | binary::*, parse, parse_try_state_string, run_try_runtime, shared_parameters::*, state, 60 | try_state_details, try_state_label, upgrade_checks_details, TryRuntimeCliCommand, 61 | }; 62 | // External export from subxt. 63 | pub use subxt::{ 64 | blocks::ExtrinsicEvents, 65 | tx::{DynamicPayload, Payload}, 66 | OnlineClient, SubstrateConfig, 67 | }; 68 | pub use templates::{Config, Parachain, Provider}; 69 | pub use utils::helpers::is_initial_endowment_valid; 70 | /// Information about the Node. External export from Zombienet-SDK. 71 | pub use zombienet_sdk::NetworkNode; 72 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/new_pallet/new_pallet_options.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueEnum; 2 | use strum_macros::{EnumIter, EnumMessage}; 3 | 4 | /// This enum is used to register from the CLI which types that are kind of usual in config traits 5 | /// are included in the pallet 6 | #[derive(Debug, Copy, Clone, PartialEq, EnumIter, EnumMessage, ValueEnum)] 7 | pub enum TemplatePalletConfigCommonTypes { 8 | /// This type will enable your pallet to emit events. 9 | #[strum( 10 | message = "RuntimeEvent", 11 | detailed_message = "This type will enable your pallet to emit events." 12 | )] 13 | RuntimeEvent, 14 | /// This type will be helpful if your pallet needs to deal with the outer RuntimeOrigin enum, 15 | /// or if your pallet needs to use custom origins. Note: If you have run the command using -o, 16 | /// this type will be added anyway. 17 | #[strum( 18 | message = "RuntimeOrigin", 19 | detailed_message = "This type will be helpful if your pallet needs to deal with the outer RuntimeOrigin enum, or if your pallet needs to use custom origins. Note: If you have run the command using -o, this type will be added anyway." 20 | )] 21 | RuntimeOrigin, 22 | /// This type will allow your pallet to interact with the native currency of the blockchain. 23 | #[strum( 24 | message = "Currency", 25 | detailed_message = "This type will allow your pallet to interact with the native currency of the blockchain." 26 | )] 27 | Currency, 28 | } 29 | 30 | /// This enum is used to determine which storage shape has a storage item in the pallet 31 | #[derive(Debug, Copy, Clone, PartialEq, EnumIter, EnumMessage, ValueEnum)] 32 | pub enum TemplatePalletStorageTypes { 33 | /// A storage value is a single value of a given type stored on-chain. 34 | #[strum( 35 | message = "StorageValue", 36 | detailed_message = "A storage value is a single value of a given type stored on-chain." 37 | )] 38 | StorageValue, 39 | /// A storage map is a mapping of keys to values of a given type stored on-chain. 40 | #[strum( 41 | message = "StorageMap", 42 | detailed_message = "A storage map is a mapping of keys to values of a given type stored on-chain." 43 | )] 44 | StorageMap, 45 | /// A wrapper around a StorageMap and a StorageValue (with the value being u32) to keep track 46 | /// of how many items are in a map. 47 | #[strum( 48 | message = "CountedStorageMap", 49 | detailed_message = "A wrapper around a StorageMap and a StorageValue (with the value being u32) to keep track of how many items are in a map." 50 | )] 51 | CountedStorageMap, 52 | /// This structure associates a pair of keys with a value of a specified type stored on-chain. 53 | #[strum( 54 | message = "StorageDoubleMap", 55 | detailed_message = "This structure associates a pair of keys with a value of a specified type stored on-chain." 56 | )] 57 | StorageDoubleMap, 58 | /// This structure associates an arbitrary number of keys with a value of a specified type 59 | /// stored on-chain. 60 | #[strum( 61 | message = "StorageNMap", 62 | detailed_message = "This structure associates an arbitrary number of keys with a value of a specified type stored on-chain." 63 | )] 64 | StorageNMap, 65 | /// A wrapper around a StorageNMap and a StorageValue (with the value being u32) to keep track 66 | /// of how many items are in a map. 67 | #[strum( 68 | message = "CountedStorageNMap", 69 | detailed_message = "A wrapper around a StorageNMap and a StorageValue (with the value being u32) to keep track of how many items are in a map." 70 | )] 71 | CountedStorageNMap, 72 | } 73 | 74 | /// This enum is used to register from the CLI which options are selected by the user to be included 75 | /// in the pallet. 76 | #[derive(Debug, Copy, Clone, PartialEq, EnumIter, EnumMessage)] 77 | pub enum TemplatePalletOptions { 78 | /// Uses a default configuration for the pallet's config trait. 79 | #[strum( 80 | message = "DefaultConfig", 81 | detailed_message = "Use a default configuration for your config trait." 82 | )] 83 | DefaultConfig, 84 | /// Adds a genesis config to the pallet. 85 | #[strum(message = "GenesisConfig", detailed_message = "Add a genesis config to your pallet.")] 86 | GenesisConfig, 87 | /// Adds a custom origin to the pallet. 88 | #[strum(message = "Custom Origin", detailed_message = "Add a custom origin to your pallet.")] 89 | CustomOrigin, 90 | } 91 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/new_parachain.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{ 4 | generator::parachain::{ChainSpec, Network}, 5 | utils::helpers::{sanitize, write_to_file}, 6 | Config, Parachain, Provider, 7 | }; 8 | use anyhow::Result; 9 | use pop_common::{ 10 | git::Git, 11 | templates::{extractor::extract_template_files, Template, Type}, 12 | }; 13 | use std::{fs, path::Path}; 14 | use walkdir::WalkDir; 15 | 16 | /// Create a new parachain. 17 | /// 18 | /// # Arguments 19 | /// 20 | /// * `template` - template to generate the parachain from. 21 | /// * `target` - location where the parachain will be created. 22 | /// * `tag_version` - version to use (`None` to use latest). 23 | /// * `config` - customization values to include in the new parachain. 24 | pub fn instantiate_template_dir( 25 | template: &Parachain, 26 | target: &Path, 27 | tag_version: Option, 28 | config: Config, 29 | ) -> Result> { 30 | sanitize(target)?; 31 | 32 | if Provider::Pop.provides(template) || template == &Parachain::ParityGeneric { 33 | return instantiate_standard_template(template, target, config, tag_version); 34 | } 35 | if Provider::OpenZeppelin.provides(template) { 36 | return instantiate_openzeppelin_template(template, target, tag_version); 37 | } 38 | let tag = Git::clone_and_degit(template.repository_url()?, target, tag_version)?; 39 | Ok(tag) 40 | } 41 | 42 | pub fn instantiate_standard_template( 43 | template: &Parachain, 44 | target: &Path, 45 | config: Config, 46 | tag_version: Option, 47 | ) -> Result> { 48 | let temp_dir = ::tempfile::TempDir::new_in(std::env::temp_dir())?; 49 | let source = temp_dir.path(); 50 | 51 | let tag = Git::clone_and_degit(template.repository_url()?, source, tag_version)?; 52 | 53 | for entry in WalkDir::new(source) { 54 | let entry = entry?; 55 | 56 | let source_path = entry.path(); 57 | let destination_path = target.join(source_path.strip_prefix(source)?); 58 | 59 | if entry.file_type().is_dir() { 60 | fs::create_dir_all(&destination_path)?; 61 | } else { 62 | fs::copy(source_path, &destination_path)?; 63 | } 64 | } 65 | let chainspec = ChainSpec { 66 | token_symbol: config.symbol, 67 | decimals: config.decimals, 68 | initial_endowment: config.initial_endowment, 69 | based_on: template.to_string(), 70 | }; 71 | use askama::Template; 72 | write_to_file( 73 | &target.join("node/src/chain_spec.rs"), 74 | chainspec.render().expect("infallible").as_ref(), 75 | )?; 76 | // Add network configuration 77 | let network = Network { node: "parachain-template-node".into() }; 78 | write_to_file(&target.join("network.toml"), network.render().expect("infallible").as_ref())?; 79 | Ok(tag) 80 | } 81 | 82 | pub fn instantiate_openzeppelin_template( 83 | template: &Parachain, 84 | target: &Path, 85 | tag_version: Option, 86 | ) -> Result> { 87 | let temp_dir = ::tempfile::TempDir::new_in(std::env::temp_dir())?; 88 | let source = temp_dir.path(); 89 | 90 | let tag = Git::clone_and_degit(template.repository_url()?, source, tag_version)?; 91 | let template_name = template.template_name_without_provider(); 92 | extract_template_files(template_name, temp_dir.path(), target, None)?; 93 | Ok(tag) 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | use anyhow::Result; 100 | use std::{env::current_dir, fs}; 101 | 102 | fn setup_template_and_instantiate() -> Result { 103 | let temp_dir = tempfile::tempdir().expect("Failed to create temp dir"); 104 | let config = Config { 105 | symbol: "DOT".to_string(), 106 | decimals: 18, 107 | initial_endowment: "1000000".to_string(), 108 | }; 109 | instantiate_standard_template(&Parachain::Standard, temp_dir.path(), config, None)?; 110 | Ok(temp_dir) 111 | } 112 | 113 | #[test] 114 | fn test_parachain_instantiate_standard_template() -> Result<()> { 115 | let temp_dir = 116 | setup_template_and_instantiate().expect("Failed to setup template and instantiate"); 117 | 118 | // Verify that the generated chain_spec.rs file contains the expected content 119 | let generated_file_content = 120 | fs::read_to_string(temp_dir.path().join("node/src/chain_spec.rs")) 121 | .expect("Failed to read file"); 122 | assert!(generated_file_content 123 | .contains("properties.insert(\"tokenSymbol\".into(), \"DOT\".into());")); 124 | assert!(generated_file_content 125 | .contains("properties.insert(\"tokenDecimals\".into(), 18.into());")); 126 | assert!(generated_file_content.contains("1000000")); 127 | 128 | // Verify network.toml contains expected content 129 | let generated_file_content = 130 | fs::read_to_string(temp_dir.path().join("network.toml")).expect("Failed to read file"); 131 | let expected_file_content = 132 | fs::read_to_string(current_dir()?.join("./templates/base/network.templ")) 133 | .expect("Failed to read file"); 134 | assert_eq!( 135 | generated_file_content, 136 | expected_file_content.replace("^^node^^", "parachain-template-node") 137 | ); 138 | 139 | Ok(()) 140 | } 141 | 142 | #[test] 143 | fn test_parachain_instantiate_openzeppelin_template() -> Result<()> { 144 | let temp_dir = tempfile::tempdir().expect("Failed to create temp dir"); 145 | instantiate_openzeppelin_template(&Parachain::OpenZeppelinEVM, temp_dir.path(), None)?; 146 | 147 | let node_manifest = 148 | pop_common::manifest::from_path(Some(&temp_dir.path().join("node/Cargo.toml"))) 149 | .expect("Failed to read file"); 150 | assert_eq!("evm-template-node", node_manifest.package().name()); 151 | 152 | let runtime_manifest = 153 | pop_common::manifest::from_path(Some(&temp_dir.path().join("runtime/Cargo.toml"))) 154 | .expect("Failed to read file"); 155 | assert_eq!("evm-runtime-template", runtime_manifest.package().name()); 156 | 157 | Ok(()) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/registry/pop.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use super::{traits::Requires, *}; 4 | use crate::{ 5 | accounts, 6 | traits::{Args, Binary}, 7 | Error, 8 | }; 9 | use pop_common::{ 10 | polkadot_sdk::sort_by_latest_semantic_version, 11 | sourcing::{traits::Source as SourceT, ArchiveFileSpec, GitHub::ReleaseArchive, Source}, 12 | target, 13 | }; 14 | use serde_json::{json, Map, Value}; 15 | use sp_core::crypto::Ss58Codec; 16 | use std::collections::HashMap; 17 | 18 | /// Pop Network makes it easy for smart contract developers to use the power of Polkadot. 19 | #[derive(Clone)] 20 | pub struct Pop(Rollup); 21 | impl Pop { 22 | /// A new instance of Pop. 23 | /// 24 | /// # Arguments 25 | /// * `id` - The rollup identifier. 26 | /// * `chain` - The identifier of the chain, as used by the chain specification. 27 | pub fn new(id: Id, chain: impl Into) -> Self { 28 | Self(Rollup::new("pop", id, chain)) 29 | } 30 | } 31 | 32 | impl SourceT for Pop { 33 | type Error = Error; 34 | /// Defines the source of a binary. 35 | fn source(&self) -> Result { 36 | // Source from GitHub release asset 37 | let binary = self.binary(); 38 | Ok(Source::GitHub(ReleaseArchive { 39 | owner: "r0gue-io".into(), 40 | repository: "pop-node".into(), 41 | tag: None, 42 | tag_pattern: Some("node-{version}".into()), 43 | prerelease: false, 44 | version_comparator: sort_by_latest_semantic_version, 45 | fallback: "v0.3.0".into(), 46 | archive: format!("{binary}-{}.tar.gz", target()?), 47 | contents: vec![ArchiveFileSpec::new(binary.into(), None, true)], 48 | latest: None, 49 | })) 50 | } 51 | } 52 | 53 | impl Binary for Pop { 54 | fn binary(&self) -> &'static str { 55 | "pop-node" 56 | } 57 | } 58 | 59 | impl Requires for Pop { 60 | /// Defines the requirements of a rollup, namely which other chains it depends on and any 61 | /// corresponding overrides to genesis state. 62 | fn requires(&self) -> Option> { 63 | let id = self.id(); 64 | let amount: u128 = 1_200_000_000_000_000_000; 65 | 66 | Some(HashMap::from([( 67 | RollupTypeId::of::(), 68 | Box::new(move |genesis_overrides: &mut Map| { 69 | let sovereign_account = accounts::sibl(id).to_ss58check(); 70 | let endowment = json!([sovereign_account, amount]); 71 | // Add sovereign account endowment 72 | genesis_overrides 73 | .entry("balances") 74 | .and_modify(|balances| { 75 | let balances = 76 | balances.as_object_mut().expect("expected balances as object"); 77 | match balances.get_mut("balances") { 78 | None => { 79 | balances.insert("balances".to_string(), json!([endowment])); 80 | }, 81 | Some(balances) => { 82 | let balances = 83 | balances.as_array_mut().expect("expected balances as array"); 84 | balances.push(endowment.clone()); 85 | }, 86 | } 87 | }) 88 | .or_insert(json!({"balances": [endowment]})); 89 | }) as Override, 90 | )])) 91 | } 92 | } 93 | 94 | impl Args for Pop { 95 | fn args(&self) -> Option> { 96 | Some(vec![ 97 | "-lpop-api::extension=debug", 98 | "-lruntime::contracts=trace", 99 | "-lruntime::revive=trace", 100 | "-lruntime::revive::strace=trace", 101 | "-lxcm=trace", 102 | "--enable-offchain-indexing=true", 103 | ]) 104 | } 105 | } 106 | 107 | impl GenesisOverrides for Pop {} 108 | 109 | impl_rollup!(Pop); 110 | 111 | #[cfg(test)] 112 | mod tests { 113 | use super::*; 114 | use pop_common::SortedSlice; 115 | use std::ptr::fn_addr_eq; 116 | 117 | #[test] 118 | fn source_works() { 119 | let pop = Pop::new(3395, "pop"); 120 | assert!(matches!( 121 | pop.source().unwrap(), 122 | Source::GitHub(ReleaseArchive { owner, repository, tag, tag_pattern, prerelease, version_comparator, fallback, archive, contents, latest }) 123 | if owner == "r0gue-io" && 124 | repository == "pop-node" && 125 | tag == None && 126 | tag_pattern == Some("node-{version}".into()) && 127 | !prerelease && 128 | fn_addr_eq(version_comparator, sort_by_latest_semantic_version as for<'a> fn(&'a mut [String]) -> SortedSlice<'a, String>) && 129 | fallback == "v0.3.0" && 130 | archive == format!("pop-node-{}.tar.gz", target().unwrap()) && 131 | contents == vec![ArchiveFileSpec::new("pop-node".into(), None, true)] && 132 | latest == None 133 | )); 134 | } 135 | 136 | #[test] 137 | fn binary_works() { 138 | let pop = Pop::new(3395, "pop"); 139 | assert_eq!(pop.binary(), "pop-node"); 140 | } 141 | 142 | #[test] 143 | fn requires_asset_hub() { 144 | let pop = Pop::new(3395, "pop"); 145 | let mut requires = pop.requires().unwrap(); 146 | let r#override = requires.get_mut(&RollupTypeId::of::()).unwrap(); 147 | let expected = json!({ 148 | "balances": { 149 | "balances": [["5Eg2fnsomjubNiqxnqSSeVwcmQYQzsHdyr79YhcJDKRYfPCL", 1200000000000000000u64]] 150 | }}); 151 | // Empty 152 | let mut overrides = Map::new(); 153 | r#override(&mut overrides); 154 | assert_eq!(Value::Object(overrides), expected); 155 | // Inserts 156 | let mut overrides = Map::new(); 157 | overrides.insert("balances".to_string(), json!({})); 158 | r#override(&mut overrides); 159 | assert_eq!(Value::Object(overrides), expected); 160 | // Updates 161 | let mut overrides = Map::new(); 162 | overrides.insert("balances".to_string(), json!({ "balances": []})); 163 | r#override(&mut overrides); 164 | assert_eq!(Value::Object(overrides), expected); 165 | } 166 | 167 | #[test] 168 | fn args_works() { 169 | let pop = Pop::new(3395, "pop"); 170 | assert_eq!( 171 | pop.args().unwrap(), 172 | vec![ 173 | "-lpop-api::extension=debug", 174 | "-lruntime::contracts=trace", 175 | "-lruntime::revive=trace", 176 | "-lruntime::revive::strace=trace", 177 | "-lxcm=trace", 178 | "--enable-offchain-indexing=true", 179 | ] 180 | ); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/relay.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::{call, DynamicPayload, Error}; 4 | use scale::{Decode, Encode}; 5 | use sp_core::twox_128; 6 | use subxt::{ 7 | config::BlockHash, 8 | dynamic::{self, Value}, 9 | events::StaticEvent, 10 | ext::{scale_decode::DecodeAsType, scale_encode::EncodeAsType}, 11 | OnlineClient, PolkadotConfig, 12 | }; 13 | 14 | /// Clears the DMPQ state for the given parachain IDs. 15 | /// 16 | /// # Arguments 17 | /// * `client` - Client for the network which state is to be modified. 18 | /// * `para_ids` - List of ids to build the keys that will be mutated. 19 | pub async fn clear_dmpq( 20 | client: OnlineClient, 21 | para_ids: &[u32], 22 | ) -> Result { 23 | // Wait for blocks to be produced. 24 | let mut sub = client 25 | .blocks() 26 | .subscribe_finalized() 27 | .await 28 | .map_err(|e| Error::SubXtError(e.into()))?; 29 | for _ in 0..2 { 30 | sub.next().await; 31 | } 32 | 33 | // Generate storage keys to be removed 34 | let clear_dmq_keys = generate_storage_keys(para_ids); 35 | 36 | // Submit calls to remove specified keys 37 | let kill_storage = construct_kill_storage_call(clear_dmq_keys); 38 | let sudo = subxt_signer::sr25519::dev::alice(); 39 | let sudo_call = call::construct_sudo_extrinsic(kill_storage); 40 | client 41 | .tx() 42 | .sign_and_submit_default(&sudo_call, &sudo) 43 | .await 44 | .map_err(|e| Error::SubXtError(e.into())) 45 | } 46 | 47 | fn construct_kill_storage_call(keys: Vec>) -> DynamicPayload { 48 | dynamic::tx( 49 | "System", 50 | "kill_storage", 51 | vec![Value::unnamed_composite(keys.into_iter().map(Value::from_bytes))], 52 | ) 53 | } 54 | 55 | fn generate_storage_keys(para_ids: &[u32]) -> Vec> { 56 | let dmp = twox_128("Dmp".as_bytes()); 57 | let dmp_queue_heads = twox_128("DownwardMessageQueueHeads".as_bytes()); 58 | let dmp_queues = twox_128("DownwardMessageQueues".as_bytes()); 59 | let mut clear_dmq_keys = Vec::>::new(); 60 | for id in para_ids { 61 | let id = id.to_le_bytes(); 62 | // DMP Queue Head 63 | let mut key = dmp.to_vec(); 64 | key.extend(&dmp_queue_heads); 65 | key.extend(sp_core::twox_64(&id)); 66 | key.extend(id); 67 | clear_dmq_keys.push(key); 68 | // DMP Queue 69 | let mut key = dmp.to_vec(); 70 | key.extend(&dmp_queues); 71 | key.extend(sp_core::twox_64(&id)); 72 | key.extend(id); 73 | clear_dmq_keys.push(key); 74 | } 75 | clear_dmq_keys 76 | } 77 | 78 | /// A supported relay chain. 79 | #[derive(Debug, PartialEq)] 80 | pub enum RelayChain { 81 | /// Paseo. 82 | PaseoLocal, 83 | /// Westend. 84 | WestendLocal, 85 | } 86 | 87 | impl RelayChain { 88 | /// Attempts to convert a chain identifier into a supported `RelayChain` variant. 89 | /// 90 | /// # Arguments 91 | /// * `id` - The relay chain identifier. 92 | pub fn from(id: &str) -> Option { 93 | match id { 94 | "paseo-local" => Some(RelayChain::PaseoLocal), 95 | "westend-local" => Some(RelayChain::WestendLocal), 96 | _ => None, 97 | } 98 | } 99 | } 100 | 101 | /// A event emitted when an id has been registered. 102 | #[derive(Debug, Encode, Decode, DecodeAsType, EncodeAsType)] 103 | #[decode_as_type(crate_path = "subxt::ext::scale_decode")] 104 | #[encode_as_type(crate_path = "subxt::ext::scale_encode")] 105 | pub struct Reserved { 106 | /// The id that has been reserved. 107 | pub para_id: u32, 108 | } 109 | impl StaticEvent for Reserved { 110 | const PALLET: &'static str = "Registrar"; 111 | const EVENT: &'static str = "Reserved"; 112 | } 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | use super::*; 117 | use sp_core::twox_64; 118 | use RelayChain::*; 119 | 120 | #[test] 121 | fn construct_kill_storage_call_works() { 122 | let keys = vec!["key".as_bytes().to_vec()]; 123 | assert_eq!( 124 | construct_kill_storage_call(keys.clone()), 125 | dynamic::tx( 126 | "System", 127 | "kill_storage", 128 | vec![Value::unnamed_composite(keys.into_iter().map(Value::from_bytes))], 129 | ) 130 | ) 131 | } 132 | 133 | #[test] 134 | fn generate_storage_keys_works() { 135 | let para_ids = vec![1_000, 4_385]; 136 | let dmp = twox_128("Dmp".as_bytes()); 137 | let dmp_queue_heads = [dmp, twox_128("DownwardMessageQueueHeads".as_bytes())].concat(); 138 | let dmp_queues = [dmp, twox_128("DownwardMessageQueues".as_bytes())].concat(); 139 | 140 | assert_eq!( 141 | generate_storage_keys(¶_ids), 142 | para_ids 143 | .iter() 144 | .flat_map(|id| { 145 | let id = id.to_le_bytes().to_vec(); 146 | [ 147 | // DMP Queue Head 148 | [dmp_queue_heads.clone(), twox_64(&id).to_vec(), id.clone()].concat(), 149 | // DMP Queue 150 | [dmp_queues.clone(), twox_64(&id).to_vec(), id].concat(), 151 | ] 152 | }) 153 | .collect::>() 154 | ) 155 | } 156 | 157 | #[test] 158 | fn supported_relay_chains() { 159 | for (s, e) in [ 160 | // Only chains with sudo supported 161 | ("paseo-local", Some(PaseoLocal)), 162 | ("westend-local", Some(WestendLocal)), 163 | ("kusama-local", None), 164 | ("polkadot-local", None), 165 | ] { 166 | assert_eq!(RelayChain::from(s), e) 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/traits.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use serde_json::{Map, Value}; 4 | 5 | /// A rollup identifier. 6 | pub type Id = u32; 7 | /// A function for providing genesis overrides. 8 | pub type Override = Box)>; 9 | /// A communication endpoint. 10 | pub type Port = u16; 11 | 12 | /// The arguments used when launching a node. 13 | pub trait Args { 14 | /// The default arguments to be used when launching a node. 15 | fn args(&self) -> Option>; 16 | } 17 | 18 | /// The binary used to launch a node. 19 | pub trait Binary { 20 | /// The name of the binary. 21 | fn binary(&self) -> &'static str; 22 | } 23 | 24 | /// A specification of a chain, providing the genesis configurations, boot nodes, and other 25 | /// parameters required to launch the chain. 26 | pub trait ChainSpec { 27 | /// The identifier of the chain, as used by the chain specification. 28 | fn chain(&self) -> &str; 29 | } 30 | 31 | /// Any overrides to genesis state. 32 | pub trait GenesisOverrides { 33 | /// Any overrides to genesis state. 34 | fn genesis_overrides(&self) -> Option { 35 | None 36 | } 37 | } 38 | 39 | /// A node. 40 | pub trait Node { 41 | /// The port to be used. 42 | fn port(&self) -> Option<&Port>; 43 | 44 | /// Set the port to be used. 45 | /// 46 | /// # Arguments 47 | /// * `port` - The port to be used. 48 | fn set_port(&mut self, port: Port); 49 | } 50 | 51 | /// An application-specific blockchain, validated by the validators of the relay chain. 52 | pub trait Rollup { 53 | /// The rollup identifier. 54 | fn id(&self) -> Id; 55 | 56 | /// The name of the chain. 57 | fn name(&self) -> &str; 58 | 59 | /// Set the rollup identifier. 60 | /// 61 | /// # Arguments 62 | /// * `id` - The rollup identifier. 63 | fn set_id(&mut self, id: Id); 64 | } 65 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/try_runtime/binary.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use pop_common::{ 4 | git::GitHub, 5 | polkadot_sdk::sort_by_latest_semantic_version, 6 | sourcing::{ 7 | filters::prefix, 8 | traits::{ 9 | enums::{Source as _, *}, 10 | Source as SourceT, 11 | }, 12 | ArchiveFileSpec, Binary, 13 | GitHub::*, 14 | Source, 15 | }, 16 | target, Error, 17 | }; 18 | use std::path::PathBuf; 19 | use strum_macros::EnumProperty; 20 | 21 | #[derive(Debug, EnumProperty, PartialEq)] 22 | pub(super) enum TryRuntimeCli { 23 | #[strum(props( 24 | Repository = "https://github.com/r0gue-io/try-runtime-cli", 25 | Binary = "try-runtime-cli", 26 | Fallback = "v0.8.0" 27 | ))] 28 | TryRuntime, 29 | } 30 | 31 | impl SourceT for TryRuntimeCli { 32 | type Error = Error; 33 | /// Defines the source of the binary required for testing runtime upgrades. 34 | fn source(&self) -> Result { 35 | // Source from GitHub release asset 36 | let repo = GitHub::parse(self.repository())?; 37 | let binary = self.binary(); 38 | Ok(Source::GitHub(ReleaseArchive { 39 | owner: repo.org, 40 | repository: repo.name, 41 | tag: None, 42 | tag_pattern: self.tag_pattern().map(|t| t.into()), 43 | prerelease: false, 44 | version_comparator: sort_by_latest_semantic_version, 45 | fallback: self.fallback().into(), 46 | archive: format!("{binary}-{}.tar.gz", target()?), 47 | contents: vec![ArchiveFileSpec::new(binary.into(), Some(binary.into()), true)], 48 | latest: None, 49 | })) 50 | } 51 | } 52 | 53 | /// Generate the source of the `try-runtime` binary on the remote repository. 54 | /// 55 | /// # Arguments 56 | /// * `cache` - The path to the directory where the binary should be cached. 57 | /// * `version` - An optional version string. If `None`, the latest available version is used. 58 | pub async fn try_runtime_generator(cache: PathBuf, version: Option<&str>) -> Result { 59 | let cli = TryRuntimeCli::TryRuntime; 60 | let name = cli.binary().to_string(); 61 | let source = cli 62 | .source()? 63 | .resolve(&name, version, cache.as_path(), |f| prefix(f, &name)) 64 | .await 65 | .into(); 66 | let binary = Binary::Source { name, source, cache: cache.to_path_buf() }; 67 | Ok(binary) 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | use tempfile::tempdir; 74 | 75 | #[tokio::test] 76 | async fn try_runtime_generator_works() -> Result<(), Error> { 77 | let temp_dir = tempdir()?.into_path(); 78 | let version = "v0.8.0"; 79 | let binary = try_runtime_generator(temp_dir.clone(), None).await?; 80 | assert!(matches!(binary, Binary::Source { name: _, source, cache } 81 | if source == Source::GitHub(ReleaseArchive { 82 | owner: "r0gue-io".to_string(), 83 | repository: "try-runtime-cli".to_string(), 84 | tag: Some(version.to_string()), 85 | tag_pattern: None, 86 | prerelease: false, 87 | version_comparator: sort_by_latest_semantic_version, 88 | fallback: version.into(), 89 | archive: format!("try-runtime-cli-{}.tar.gz", target()?), 90 | contents: ["try-runtime-cli"].map(|b| ArchiveFileSpec::new(b.into(), Some(b.into()), true)).to_vec(), 91 | latest: binary.latest().map(|l| l.to_string()), 92 | }).into() && 93 | cache == temp_dir.as_path() 94 | )); 95 | Ok(()) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/try_runtime/parse.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use sp_version::StateVersion; 19 | 20 | use crate::Error; 21 | 22 | /// Parse a block hash from a string. 23 | pub fn hash(block_hash: &str) -> Result { 24 | let (block_hash, offset) = if let Some(block_hash) = block_hash.strip_prefix("0x") { 25 | (block_hash, 2) 26 | } else { 27 | (block_hash, 0) 28 | }; 29 | 30 | if let Some(pos) = block_hash.chars().position(|c| !c.is_ascii_hexdigit()) { 31 | Err(Error::ParamParsingError(format!( 32 | "Expected block hash, found illegal hex character at position: {}", 33 | offset + pos, 34 | ))) 35 | } else { 36 | Ok(block_hash.into()) 37 | } 38 | } 39 | 40 | /// Parse a URL from a string. 41 | pub fn url(s: &str) -> Result { 42 | if s.starts_with("ws://") || s.starts_with("wss://") { 43 | Ok(s.to_string()) 44 | } else { 45 | Err(Error::ParamParsingError( 46 | "not a valid WS(S) url: must start with 'ws://' or 'wss://'".to_string(), 47 | )) 48 | } 49 | } 50 | 51 | /// Parse a state version from a string. 52 | pub fn state_version(s: &str) -> Result { 53 | s.parse::() 54 | .map_err(|_| ()) 55 | .and_then(StateVersion::try_from) 56 | .map_err(|_| Error::ParamParsingError("Invalid state version.".to_string())) 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | 63 | #[test] 64 | fn parse_hash_works() { 65 | assert!(hash("0x1234567890abcdef").is_ok()); 66 | assert!(hash("1234567890abcdef").is_ok()); 67 | assert!(hash("0x1234567890abcdefg").is_err()); 68 | assert!(hash("1234567890abcdefg").is_err()); 69 | } 70 | 71 | #[test] 72 | fn parse_url_works() { 73 | assert!(url("ws://localhost:9944").is_ok()); 74 | assert!(url("wss://localhost:9944").is_ok()); 75 | assert!(url("http://localhost:9944").is_err()); 76 | assert!(url("https://localhost:9944").is_err()); 77 | } 78 | 79 | #[test] 80 | fn parse_state_version_works() { 81 | assert!(state_version("0").is_ok()); 82 | assert!(state_version("1").is_ok()); 83 | assert!(state_version("100").is_err()); 84 | assert!(state_version("200").is_err()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/try_runtime/shared_parameters.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use super::parse::state_version; 19 | use sc_cli::{ 20 | WasmExecutionMethod, WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, 21 | DEFAULT_WASM_EXECUTION_METHOD, 22 | }; 23 | use sp_runtime::StateVersion; 24 | use std::{path::PathBuf, str::FromStr}; 25 | 26 | /// Shared parameters derived from the fields of struct [SharedParams]. 27 | const SHARED_PARAMS: [&str; 7] = [ 28 | "--runtime", 29 | "--disable-spec-name-check", 30 | "--wasm-execution", 31 | "--wasm-instantiation-strategy", 32 | "--heap-pages", 33 | "--export-proof", 34 | "--overwrite-state-version", 35 | ]; 36 | 37 | /// Shared parameters of the `try-runtime` commands 38 | #[derive(Debug, Clone, clap::Parser)] 39 | #[group(skip)] 40 | pub struct SharedParams { 41 | /// The runtime to use. 42 | /// 43 | /// Must be a path to a wasm blob, compiled with `try-runtime` feature flag. 44 | /// 45 | /// Or, `existing`, indicating that you don't want to overwrite the runtime. This will use 46 | /// whatever comes from the remote node, or the snapshot file. This will most likely not work 47 | /// against a remote node, as no (sane) blockchain should compile its onchain wasm with 48 | /// `try-runtime` feature. 49 | #[arg(long, default_value = "existing")] 50 | pub runtime: Runtime, 51 | 52 | /// Whether to disable enforcing the new runtime `spec_name` matches the existing `spec_name`. 53 | #[clap(long, default_value = "false", default_missing_value = "true")] 54 | pub disable_spec_name_check: bool, 55 | 56 | /// Type of wasm execution used. 57 | #[arg( 58 | long = "wasm-execution", 59 | value_name = "METHOD", 60 | value_enum, 61 | ignore_case = true, 62 | default_value_t = DEFAULT_WASM_EXECUTION_METHOD, 63 | )] 64 | pub wasm_method: WasmExecutionMethod, 65 | 66 | /// The WASM instantiation method to use. 67 | /// 68 | /// Only has an effect when `wasm-execution` is set to `compiled`. 69 | #[arg( 70 | long = "wasm-instantiation-strategy", 71 | value_name = "STRATEGY", 72 | default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, 73 | value_enum, 74 | )] 75 | pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, 76 | 77 | /// The number of 64KB pages to allocate for Wasm execution. Defaults to 78 | /// [`sc_service::Configuration.default_heap_pages`]. 79 | #[arg(long)] 80 | pub heap_pages: Option, 81 | 82 | /// Path to a file to export the storage proof into (as a JSON). 83 | /// If several blocks are executed, the path is interpreted as a folder 84 | /// where one file per block will be written (named `{block_number}-{block_hash}`). 85 | #[clap(long)] 86 | pub export_proof: Option, 87 | 88 | /// Overwrite the `state_version`. 89 | /// 90 | /// Otherwise `remote-externalities` will automatically set the correct state version. 91 | #[arg(long, value_parser = state_version)] 92 | pub overwrite_state_version: Option, 93 | } 94 | 95 | impl Default for SharedParams { 96 | fn default() -> Self { 97 | SharedParams { 98 | runtime: Runtime::Existing, 99 | disable_spec_name_check: false, 100 | wasm_method: DEFAULT_WASM_EXECUTION_METHOD, 101 | wasmtime_instantiation_strategy: DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, 102 | heap_pages: None, 103 | export_proof: None, 104 | overwrite_state_version: None, 105 | } 106 | } 107 | } 108 | 109 | impl SharedParams { 110 | /// Check if the given argument is a shared parameter. 111 | pub fn has_argument(arg: &str) -> bool { 112 | SHARED_PARAMS.iter().any(|a| arg.starts_with(a)) 113 | } 114 | } 115 | 116 | /// Source of the runtime. 117 | #[derive(Debug, Clone)] 118 | pub enum Runtime { 119 | /// Use the given path to the wasm binary file. 120 | /// 121 | /// It must have been compiled with `try-runtime`. 122 | Path(PathBuf), 123 | 124 | /// Use the code of the remote node, or the snapshot. 125 | /// 126 | /// In almost all cases, this is not what you want, because the code in the remote node does 127 | /// not have any of the try-runtime custom runtime APIs. 128 | Existing, 129 | } 130 | 131 | impl FromStr for Runtime { 132 | type Err = String; 133 | 134 | fn from_str(s: &str) -> Result { 135 | Ok(match s.to_lowercase().as_ref() { 136 | "existing" => Runtime::Existing, 137 | x => Runtime::Path(x.into()), 138 | }) 139 | } 140 | } 141 | 142 | #[cfg(test)] 143 | mod tests { 144 | use super::*; 145 | 146 | #[test] 147 | fn is_shared_params_works() { 148 | assert!(SHARED_PARAMS.into_iter().all(SharedParams::has_argument)); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/try_runtime/state.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use std::path::PathBuf; 4 | use strum::{Display, EnumDiscriminants}; 5 | use strum_macros::{AsRefStr, EnumMessage, EnumString, VariantArray}; 6 | 7 | /// The runtime *state*. 8 | #[derive(Clone, Debug, clap::Subcommand, EnumDiscriminants)] 9 | #[strum_discriminants(derive(AsRefStr, EnumString, EnumMessage, VariantArray, Display))] 10 | #[strum_discriminants(name(StateCommand))] 11 | pub enum State { 12 | /// A live chain. 13 | #[strum_discriminants(strum( 14 | serialize = "live", 15 | message = "Live", 16 | detailed_message = "Run the migrations on top of live state.", 17 | ))] 18 | Live(LiveState), 19 | 20 | /// A state snapshot. 21 | #[strum_discriminants(strum( 22 | serialize = "snap", 23 | message = "Snapshot", 24 | detailed_message = "Run the migrations on top of a chain snapshot." 25 | ))] 26 | Snap { 27 | /// Path to the snapshot file. 28 | #[clap(short = 'p', long = "path", alias = "snapshot-path")] 29 | path: Option, 30 | }, 31 | } 32 | 33 | /// A `Live` variant for [`State`] 34 | #[derive(Debug, Default, Clone, clap::Args)] 35 | pub struct LiveState { 36 | /// The url to connect to. 37 | #[arg( 38 | short, 39 | long, 40 | value_parser = super::parse::url, 41 | )] 42 | pub uri: Option, 43 | 44 | /// The block hash at which to fetch the state. 45 | /// 46 | /// If not provided the latest finalised head is used. 47 | #[arg( 48 | short, 49 | long, 50 | value_parser = super::parse::hash, 51 | )] 52 | pub at: Option, 53 | 54 | /// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will 55 | /// be scraped. 56 | /// 57 | /// This is equivalent to passing `xx_hash_64(pallet)` to `--hashed_prefixes`. 58 | #[arg(short, long, num_args = 1..)] 59 | pub pallet: Vec, 60 | 61 | /// Storage entry key prefixes to scrape and inject into the test externalities. Pass as 0x 62 | /// prefixed hex strings. By default, all keys are scraped and included. 63 | #[arg(long = "prefix", value_parser = super::parse::hash, num_args = 1..)] 64 | pub hashed_prefixes: Vec, 65 | 66 | /// Fetch the child-keys. 67 | /// 68 | /// Default is `false`, if `--pallets` are specified, `true` otherwise. In other 69 | /// words, if you scrape the whole state the child tree data is included out of the box. 70 | /// Otherwise, it must be enabled explicitly using this flag. 71 | #[arg(long)] 72 | pub child_tree: bool, 73 | } 74 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/utils/helpers.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use crate::errors::Error; 4 | use std::{ 5 | fs::{self, OpenOptions}, 6 | io::{self, stdin, stdout, Write}, 7 | path::Path, 8 | }; 9 | 10 | pub(crate) fn sanitize(target: &Path) -> Result<(), Error> { 11 | if target.exists() { 12 | print!("\"{}\" directory exists. Do you want to clean it? [y/n]: ", target.display()); 13 | stdout().flush()?; 14 | 15 | let mut input = String::new(); 16 | stdin().read_line(&mut input)?; 17 | 18 | if input.trim().to_lowercase() == "y" { 19 | fs::remove_dir_all(target).map_err(|_| Error::Aborted)?; 20 | } else { 21 | return Err(Error::Aborted); 22 | } 23 | } 24 | Ok(()) 25 | } 26 | 27 | /// Check if the initial endowment input by the user is a valid balance. 28 | /// 29 | /// # Arguments 30 | /// 31 | /// * `initial_endowment` - initial endowment amount to be checked for validity. 32 | pub fn is_initial_endowment_valid(initial_endowment: &str) -> bool { 33 | initial_endowment.parse::().is_ok() || 34 | is_valid_bitwise_left_shift(initial_endowment).is_ok() 35 | } 36 | 37 | // Auxiliary method to check if the endowment input with a shift left (1u64 << 60) format is valid. 38 | // Parse the self << rhs format and check the shift left operation is valid. 39 | fn is_valid_bitwise_left_shift(initial_endowment: &str) -> Result { 40 | let v: Vec<&str> = initial_endowment.split(" << ").collect(); 41 | if v.len() < 2 { 42 | return Err(Error::EndowmentError); 43 | } 44 | let left = v[0] 45 | .split('u') // parse 1u64 characters 46 | .take(1) 47 | .collect::() 48 | .parse::() 49 | .map_err(|_e| Error::EndowmentError)?; 50 | let right = v[1] 51 | .chars() 52 | .filter(|c| c.is_numeric()) // parse 1u64 characters 53 | .collect::() 54 | .parse::() 55 | .map_err(|_e| Error::EndowmentError)?; 56 | left.checked_shl(right).ok_or(Error::EndowmentError) 57 | } 58 | 59 | pub(crate) fn write_to_file(path: &Path, contents: &str) -> Result<(), Error> { 60 | let mut file = OpenOptions::new() 61 | .write(true) 62 | .truncate(true) 63 | .create(true) 64 | .open(path) 65 | .map_err(Error::RustfmtError)?; 66 | 67 | file.write_all(contents.as_bytes()).map_err(Error::RustfmtError)?; 68 | 69 | if path.extension().is_some_and(|ext| ext == "rs") { 70 | let output = std::process::Command::new("rustfmt") 71 | .arg(path.to_str().unwrap()) 72 | .output() 73 | .map_err(Error::RustfmtError)?; 74 | 75 | if !output.status.success() { 76 | return Err(Error::RustfmtError(io::Error::new( 77 | io::ErrorKind::Other, 78 | "rustfmt exited with non-zero status code", 79 | ))); 80 | } 81 | } 82 | 83 | Ok(()) 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::*; 89 | use crate::{generator::parachain::ChainSpec, Parachain}; 90 | use askama::Template; 91 | use tempfile::tempdir; 92 | 93 | #[test] 94 | fn test_write_to_file() -> Result<(), Box> { 95 | let temp_dir = tempdir()?; 96 | let chainspec = ChainSpec { 97 | token_symbol: "DOT".to_string(), 98 | decimals: 6, 99 | initial_endowment: "1000000".to_string(), 100 | based_on: Parachain::Standard.to_string(), 101 | }; 102 | let file_path = temp_dir.path().join("file.rs"); 103 | let _ = fs::write(&file_path, ""); 104 | write_to_file(&file_path, chainspec.render().expect("infallible").as_ref())?; 105 | let generated_file_content = 106 | fs::read_to_string(temp_dir.path().join("file.rs")).expect("Failed to read file"); 107 | assert!(generated_file_content 108 | .contains("properties.insert(\"tokenSymbol\".into(), \"DOT\".into());")); 109 | assert!(generated_file_content 110 | .contains("properties.insert(\"tokenDecimals\".into(), 6.into());")); 111 | assert!(generated_file_content.contains("1000000")); 112 | assert!(generated_file_content.contains( 113 | "properties.insert(\"basedOn\".into(), \"r0gue-io/base-parachain\".into());" 114 | )); 115 | 116 | Ok(()) 117 | } 118 | 119 | #[test] 120 | fn test_is_initial_endowment_valid() { 121 | assert_eq!(is_initial_endowment_valid("100000"), true); 122 | assert_eq!(is_initial_endowment_valid("1u64 << 60"), true); 123 | assert_eq!(is_initial_endowment_valid("wrong"), false); 124 | assert_eq!(is_initial_endowment_valid(" "), false); 125 | } 126 | 127 | #[test] 128 | fn test_left_shift() { 129 | // Values from https://stackoverflow.com/questions/56392875/how-can-i-initialize-a-users-balance-in-a-substrate-blockchain 130 | assert_eq!(is_valid_bitwise_left_shift("1 << 60").unwrap(), 1152921504606846976); 131 | let result = is_valid_bitwise_left_shift("wrong"); 132 | assert!(result.is_err()); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /crates/pop-parachains/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pub mod helpers; 4 | -------------------------------------------------------------------------------- /crates/pop-parachains/templates/base/network.templ: -------------------------------------------------------------------------------- 1 | [relaychain] 2 | chain = "paseo-local" 3 | 4 | [[relaychain.nodes]] 5 | name = "alice" 6 | validator = true 7 | 8 | [[relaychain.nodes]] 9 | name = "bob" 10 | validator = true 11 | 12 | [[parachains]] 13 | id = 2000 14 | default_command = "./target/release/^^node^^" 15 | 16 | [[parachains.collators]] 17 | name = "collator-01" -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/Cargo.templ: -------------------------------------------------------------------------------- 1 | {{- let using_currency = pallet_common_types|contains(TemplatePalletConfigCommonTypes::Currency) -}} 2 | [package] 3 | name = "pallet-^^name|lower^^" 4 | authors = ["^^authors^^"] 5 | description = "^^description^^" 6 | version = "0.1.0" 7 | license = "Unlicense" 8 | {{- if pallet_in_workspace }} 9 | edition.workspace = true 10 | repository.workspace = true 11 | {{- else }} 12 | edition = "2021" 13 | {{- endif }} 14 | 15 | [package.metadata.docs.rs] 16 | targets = ["x86_64-unknown-linux-gnu"] 17 | 18 | [dependencies] 19 | codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ 20 | "derive", 21 | ] } 22 | scale-info = { version = "2.11.1", default-features = false, features = [ 23 | "derive", 24 | ] } 25 | frame = { version = "0.3.0", package = "polkadot-sdk-frame", default-features = false, features = ["experimental", "runtime"] } 26 | 27 | {{ if using_currency }} 28 | [dev-dependencies] 29 | pallet-balances = { version = "35.0.0" } 30 | {{- endif }} 31 | 32 | [features] 33 | default = ["std"] 34 | std = [ 35 | "codec/std", 36 | "frame/std", 37 | "scale-info/std", 38 | ] 39 | runtime-benchmarks = ["frame/runtime-benchmarks"] 40 | try-runtime = ["frame/try-runtime"] 41 | -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/advanced_mode/src/benchmarking.rs.templ: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[allow(unused)] 4 | use crate::{frame_system::RawOrigin, Pallet, mock::{new_test_ext, Test}}; 5 | use frame::deps::frame_benchmarking::v2::*; 6 | 7 | #[benchmarks] 8 | mod benchmarks { 9 | use super::*; 10 | 11 | #[benchmark] 12 | fn some_benchmark(){} 13 | 14 | impl_benchmark_test_suite!(Pallet, new_test_ext(), Test); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/advanced_mode/src/config_preludes.rs.templ: -------------------------------------------------------------------------------- 1 | {{- let using_runtime_event = pallet_common_types|contains(TemplatePalletConfigCommonTypes::RuntimeEvent) -}} 2 | {{- let using_runtime_origin = pallet_common_types|contains(TemplatePalletConfigCommonTypes::RuntimeOrigin) -}} 3 | {{- let using_currency = pallet_common_types|contains(TemplatePalletConfigCommonTypes::Currency) -}} 4 | use super::*; 5 | use frame::runtime::prelude::derive_impl; 6 | 7 | /// Provides a viable default config that can be used with 8 | /// [`derive_impl`] to derive a testing pallet config 9 | /// based on this one. 10 | pub struct TestDefaultConfig; 11 | 12 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)] 13 | impl frame::deps::frame_system::DefaultConfig for TestDefaultConfig {} 14 | 15 | #[register_default_impl(TestDefaultConfig)] 16 | impl DefaultConfig for TestDefaultConfig { 17 | {{- if using_runtime_event -}} 18 | #[inject_runtime_type] 19 | type RuntimeEvent = (); 20 | {{- endif -}} 21 | {{- if using_runtime_origin || pallet_custom_origin -}} 22 | #[inject_runtime_type] 23 | type RuntimeOrigin = (); 24 | {{- endif -}} 25 | {{- if using_currency -}} 26 | #[inject_runtime_type] 27 | type RuntimeHoldReason = (); 28 | #[inject_runtime_type] 29 | type RuntimeFreezeReason = (); 30 | {{- endif -}} 31 | } 32 | 33 | /// Default configurations of this pallet in a solochain environment. 34 | pub struct SolochainDefaultConfig; 35 | 36 | #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig, no_aggregated_types)] 37 | impl frame::deps::frame_system::DefaultConfig for SolochainDefaultConfig {} 38 | 39 | #[register_default_impl(SolochainDefaultConfig)] 40 | impl DefaultConfig for SolochainDefaultConfig { 41 | {{- if using_runtime_event -}} 42 | #[inject_runtime_type] 43 | type RuntimeEvent = (); 44 | {{- endif -}} 45 | {{- if using_runtime_origin || pallet_custom_origin -}} 46 | #[inject_runtime_type] 47 | type RuntimeOrigin = (); 48 | {{- endif -}} 49 | {{- if using_currency -}} 50 | #[inject_runtime_type] 51 | type RuntimeHoldReason = (); 52 | #[inject_runtime_type] 53 | type RuntimeFreezeReason = (); 54 | {{- endif -}} 55 | } 56 | 57 | /// Default configurations of this pallet in a solochain environment. 58 | pub struct RelayChainDefaultConfig; 59 | 60 | #[derive_impl(frame_system::config_preludes::RelayChainDefaultConfig, no_aggregated_types)] 61 | impl frame::deps::frame_system::DefaultConfig for RelayChainDefaultConfig {} 62 | 63 | #[register_default_impl(RelayChainDefaultConfig)] 64 | impl DefaultConfig for RelayChainDefaultConfig { 65 | {{- if using_runtime_event -}} 66 | #[inject_runtime_type] 67 | type RuntimeEvent = (); 68 | {{- endif -}} 69 | {{- if using_runtime_origin || pallet_custom_origin -}} 70 | #[inject_runtime_type] 71 | type RuntimeOrigin = (); 72 | {{- endif -}} 73 | {{- if using_currency -}} 74 | #[inject_runtime_type] 75 | type RuntimeHoldReason = (); 76 | #[inject_runtime_type] 77 | type RuntimeFreezeReason = (); 78 | {{- endif -}} 79 | } 80 | 81 | /// Default configurations of this pallet in a solochain environment. 82 | pub struct ParaChainDefaultConfig; 83 | 84 | #[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig, no_aggregated_types)] 85 | impl frame::deps::frame_system::DefaultConfig for ParaChainDefaultConfig {} 86 | 87 | #[register_default_impl(ParaChainDefaultConfig)] 88 | impl DefaultConfig for ParaChainDefaultConfig{ 89 | {{- if using_runtime_event -}} 90 | #[inject_runtime_type] 91 | type RuntimeEvent = (); 92 | {{- endif -}} 93 | {{- if using_runtime_origin || pallet_custom_origin -}} 94 | #[inject_runtime_type] 95 | type RuntimeOrigin = (); 96 | {{- endif -}} 97 | {{- if using_currency -}} 98 | #[inject_runtime_type] 99 | type RuntimeHoldReason = (); 100 | #[inject_runtime_type] 101 | type RuntimeFreezeReason = (); 102 | {{- endif -}} 103 | } -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/advanced_mode/src/mock.rs.templ: -------------------------------------------------------------------------------- 1 | {{- let using_runtime_event = pallet_common_types|contains(TemplatePalletConfigCommonTypes::RuntimeEvent) -}} 2 | {{- let using_runtime_origin = pallet_common_types|contains(TemplatePalletConfigCommonTypes::RuntimeOrigin) -}} 3 | {{- let using_currency = pallet_common_types|contains(TemplatePalletConfigCommonTypes::Currency) -}} 4 | use crate::{self as pallet_^^name|lower^^, *}; 5 | 6 | pub use frame::{ 7 | deps::{frame_support::runtime, sp_io::TestExternalities}, 8 | runtime::prelude::*, 9 | testing_prelude::*, 10 | {{- if using_currency }} 11 | traits::fungible::Mutate 12 | {{- endif }} 13 | }; 14 | 15 | type Block = MockBlock; 16 | 17 | {{ if using_currency }} 18 | type Balance = u64; 19 | {{- endif }} 20 | 21 | #[runtime] 22 | mod runtime{ 23 | #[runtime::runtime] 24 | #[runtime::derive( 25 | RuntimeCall, 26 | RuntimeEvent, 27 | RuntimeError, 28 | RuntimeOrigin, 29 | RuntimeFreezeReason, 30 | RuntimeHoldReason, 31 | RuntimeSlashReason, 32 | RuntimeLockId, 33 | RuntimeTask 34 | )] 35 | pub struct Test; 36 | 37 | #[runtime::pallet_index(0)] 38 | pub type System = frame_system; 39 | 40 | {{ if using_currency }} 41 | #[runtime::pallet_index(1)] 42 | pub type Balances = pallet_balances; 43 | {{- endif }} 44 | 45 | #[runtime::pallet_index(2)] 46 | pub type ^^name|capitalize^^ = pallet_^^name|lower^^; 47 | } 48 | 49 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] 50 | impl frame_system::Config for Test { 51 | type Block = Block; 52 | {{- if using_currency -}} 53 | type AccountData = pallet_balances::AccountData; 54 | {{- endif -}} 55 | } 56 | 57 | {{ if using_currency -}} 58 | #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] 59 | impl pallet_balances::Config for Test { 60 | type AccountStore = System; 61 | } 62 | {{- endif }} 63 | 64 | {{- if pallet_default_config }} 65 | #[derive_impl(pallet_^^name|lower^^::config_preludes::TestDefaultConfig)] 66 | {{- endif }} 67 | impl pallet_^^name|lower^^::Config for Test { 68 | {{- if using_runtime_event && !pallet_default_config -}} 69 | type RuntimeEvent = RuntimeEvent; 70 | {{- endif -}} 71 | {{- if (using_runtime_origin || pallet_custom_origin) && !pallet_default_config -}} 72 | type RuntimeOrigin = RuntimeOrigin; 73 | {{- endif -}} 74 | {{- if using_currency -}} 75 | type Currency = Balances; 76 | {{- if !pallet_default_config }} 77 | type RuntimeHoldReason = RuntimeHoldReason; 78 | type RuntimeFreezeReason = RuntimeFreezeReason; 79 | {{- endif -}} 80 | {{- endif -}} 81 | } 82 | 83 | pub(crate) struct StateBuilder{ 84 | {{- if using_currency -}} 85 | balances: Vec<(::AccountId, Balance)> 86 | {{- endif -}} 87 | } 88 | 89 | impl Default for StateBuilder{ 90 | fn default() -> Self { 91 | Self { 92 | {{-if using_currency -}} 93 | balances: vec![] 94 | {{- endif -}} 95 | } 96 | } 97 | } 98 | 99 | impl StateBuilder { 100 | {{- if using_currency -}} 101 | /// This function helps to construct a initial state where some accounts have balances 102 | fn add_balance( 103 | mut self, 104 | who: ::AccountId, 105 | amount: Balance, 106 | ) -> Self { 107 | self.balances.push((who, amount)); 108 | self 109 | } 110 | {{- endif }} 111 | 112 | pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) { 113 | let mut ext: TestExternalities = frame_system::GenesisConfig::::default() 114 | .build_storage() 115 | .unwrap() 116 | .into(); 117 | 118 | // Test setup 119 | ext.execute_with(|| { 120 | System::set_block_number(1); 121 | {{- if using_currency -}} 122 | self.balances.iter().for_each(|(who, amount)| { 123 | ::Currency::set_balance(who, *amount); 124 | }) 125 | {{- endif }} 126 | }); 127 | 128 | ext.execute_with(test); 129 | 130 | // Test assertions 131 | ext.execute_with(|| { 132 | 133 | }); 134 | } 135 | } 136 | 137 | pub fn new_test_ext() -> TestExternalities { 138 | frame_system::GenesisConfig::::default() 139 | .build_storage() 140 | .unwrap() 141 | .into() 142 | } -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/advanced_mode/src/pallet_logic.rs.templ: -------------------------------------------------------------------------------- 1 | // A mod containing the try_state logic. 2 | mod try_state; 3 | {{- if pallet_custom_origin }} 4 | // A mod containing logic to manage the pallet's custom origin 5 | mod origin; 6 | {{- endif -}} -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/advanced_mode/src/pallet_logic/origin.rs.templ: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | impl Pallet { 4 | /// A helpful function your pallet may use to convert a external origin to the custom AllOrigins 5 | pub(crate) fn origin_to_all_origins(_origin: OriginFor) -> Result, DispatchError>{ 6 | Ok(>::from(>::AllOrigins)) 7 | } 8 | 9 | /// A helpful function your pallet may use to convert a external origin to the custom OnlyAuthenticatedAccounts 10 | pub(crate) fn origin_to_only_authenticated_accounts(origin: OriginFor) -> Result, DispatchError>{ 11 | Ok(>::from(>::OnlyAuthenticatedAccounts(ensure_signed(origin)?))) 12 | } 13 | } 14 | 15 | impl, OuterOrigin>> + From>> EnsureOrigin for Origin 16 | { 17 | type Success = CustomOriginSuccess; 18 | 19 | fn try_origin(outer_origin: OuterOrigin) -> Result{ 20 | outer_origin.into().and_then(|origin|match origin{ 21 | Origin::AllOrigins if >::exists() => Ok(Self::Success::Unit), 22 | Origin::OnlyAuthenticatedAccounts(account) if >::contains_key(&account) => Ok(Self::Success::AccountId(account)), 23 | other => Err(OuterOrigin::from(other)) 24 | }) 25 | } 26 | 27 | #[cfg(feature= "runtime-benchmarks")] 28 | fn try_successful_origin() -> Result{ 29 | Ok(OuterOrigin::from(>::AllOrigins)) 30 | } 31 | } -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/advanced_mode/src/pallet_logic/try_state.rs.templ: -------------------------------------------------------------------------------- 1 | use crate::pallet::*; 2 | 3 | impl Pallet { 4 | #[cfg(feature = "try-runtime")] 5 | pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { 6 | // TODO: Write here your try_state logic if needed 7 | Ok(()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/advanced_mode/src/tests.rs.templ: -------------------------------------------------------------------------------- 1 | use crate::{*, mock::*}; 2 | 3 | // Some utils useful for testing 4 | mod utils; 5 | 6 | // You can split your tests in modules and add them here, or just write them down :) -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/advanced_mode/src/tests/utils.rs.templ: -------------------------------------------------------------------------------- 1 | use crate::tests::*; 2 | /// A function useful to advance a block :) If your pallet implements on_initialize, this is a good place to run it. 3 | pub fn next_block() { 4 | System::set_block_number(System::block_number() + 1); 5 | System::on_initialize(System::block_number()); 6 | // ^^name|capitalize^^::on_initialize(System::block_number()); 7 | #[cfg(feature = "try-runtime")] 8 | ^^name|capitalize^^::try_state(System::block_number()).expect("State corrupted"); 9 | } 10 | 11 | /// A function useful to run til the desired block. If your pallet implements on_finalize, this is a good place to run it. 12 | pub fn run_to_block(n: BlockNumberFor) { 13 | while System::block_number() < n { 14 | if System::block_number() > 1 { 15 | System::on_finalize(System::block_number()); 16 | // ^^name|capitalize^^::on_finalize(System::block_number()); 17 | } 18 | next_block(); 19 | } 20 | } 21 | 22 | /// Typically in tests, AccountId is just a number. With this macro you can give them a friendly ident 23 | /// # Example 24 | /// ```rust 25 | /// give_accounts_names!(alice, bob, charlie); 26 | /// assert_eq!(alice, 1); 27 | /// assert_eq!(bob, 2); 28 | /// assert_eq!(charlie, 3); 29 | /// ``` 30 | #[macro_export] 31 | macro_rules! give_accounts_names{ 32 | ($($account: ident),+) => { 33 | let mut counter: ::AccountId = 0; 34 | $( 35 | counter +=1; 36 | let $account = counter; 37 | )+ 38 | }; 39 | } 40 | 41 | /// This macro's useful to test that an unsigned origin cannot call an extrinsic requiring a signed origin 42 | #[macro_export] 43 | macro_rules! not_signed_call_triggers_bad_origin_test{ 44 | ($test_name: ident, $extrinsic: ident, $($arg: expr),*) =>{ 45 | #[test] 46 | fn $test_name(){ 47 | new_test_ext().execute_with(||{ 48 | assert_noop!(^^name|capitalize^^::$extrinsic(RuntimeOrigin::none(), $($arg),*), DispatchError::BadOrigin); 49 | }) 50 | } 51 | }; 52 | } -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/advanced_mode/src/types.rs.templ: -------------------------------------------------------------------------------- 1 | {{- let using_runtime_origin = pallet_common_types|contains(TemplatePalletConfigCommonTypes::RuntimeOrigin) -}} 2 | {{- let using_currency = pallet_common_types|contains(TemplatePalletConfigCommonTypes::Currency) -}} 3 | use crate::*; 4 | 5 | {{ if using_runtime_origin || pallet_custom_origin }} 6 | /// An alias referencing the RuntimeOrigin config type. 7 | pub type RuntimeLocalOrigin = ::RuntimeOrigin; 8 | {{- endif }} 9 | 10 | {{ if using_currency }} 11 | /// An alias referencing the native currency's balance of an account. 12 | pub type BalanceOf = <::Currency as fungible::Inspect<::AccountId>>::Balance; 13 | {{- endif }} 14 | 15 | {{ if pallet_storage|contains(TemplatePalletStorageTypes::StorageMap) }} 16 | /// A fancy struct that can be used to store information together in storage. It's storing some bytes chosen and the Block number when it's registered SCALE compact encoded. This struct will be linked to a AccountId via MyStorageMap 17 | #[derive(TypeInfo, Encode, Decode, MaxEncodedLen, PartialEq, RuntimeDebugNoBound)] 18 | #[scale_info(skip_type_params(T))] 19 | pub struct AccountInfo { 20 | // Awesome bytes 21 | pub my_bytes: [u8;10], 22 | // The block where the account registered the phrase 23 | #[codec(compact)] 24 | pub block_joined: BlockNumberFor, 25 | } 26 | {{ endif }} 27 | 28 | {{ if pallet_custom_origin }} 29 | pub enum CustomOriginSuccess{ 30 | AccountId(T::AccountId), 31 | Unit 32 | } 33 | {{ endif }} 34 | -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/simple_mode/src/benchmarking.rs.templ: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "runtime-benchmarks")] 2 | 3 | use super::*; 4 | use frame::deps::frame_benchmarking::v2::*; 5 | 6 | #[benchmarks] 7 | mod benchmarks { 8 | use super::*; 9 | #[cfg(test)] 10 | use crate::pallet::Pallet as Template; 11 | use frame_system::RawOrigin; 12 | 13 | #[benchmark] 14 | fn do_something() { 15 | let caller: T::AccountId = whitelisted_caller(); 16 | #[extrinsic_call] 17 | do_something(RawOrigin::Signed(caller), 100); 18 | 19 | assert_eq!(Something::::get().map(|v| v.block_number), Some(100u32.into())); 20 | } 21 | 22 | #[benchmark] 23 | fn cause_error() { 24 | let caller: T::AccountId = whitelisted_caller(); 25 | Something::::put(CompositeStruct { block_number: 100u32.into(), someone: caller.clone() }); 26 | #[extrinsic_call] 27 | cause_error(RawOrigin::Signed(caller)); 28 | 29 | assert_eq!(Something::::get().map(|v| v.block_number), Some(101u32.into())); 30 | } 31 | 32 | impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test); 33 | } -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/simple_mode/src/mock.rs.templ: -------------------------------------------------------------------------------- 1 | use crate::frame_system::{mocking::MockBlock, GenesisConfig}; 2 | use frame::{deps::frame_support::{runtime, derive_impl, weights::constants::RocksDbWeight}, runtime::prelude::*, testing_prelude::*}; 3 | 4 | // Configure a mock runtime to test the pallet. 5 | #[runtime] 6 | mod test_runtime { 7 | #[runtime::runtime] 8 | #[runtime::derive( 9 | RuntimeCall, 10 | RuntimeEvent, 11 | RuntimeError, 12 | RuntimeOrigin, 13 | RuntimeFreezeReason, 14 | RuntimeHoldReason, 15 | RuntimeSlashReason, 16 | RuntimeLockId, 17 | RuntimeTask 18 | )] 19 | pub struct Test; 20 | 21 | #[runtime::pallet_index(0)] 22 | pub type System = frame_system; 23 | #[runtime::pallet_index(1)] 24 | pub type ^^name|capitalize^^ = crate; 25 | } 26 | 27 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] 28 | impl frame_system::Config for Test { 29 | type Nonce = u64; 30 | type Block = MockBlock; 31 | type BlockHashCount = ConstU64<250>; 32 | type DbWeight = RocksDbWeight; 33 | } 34 | 35 | impl crate::Config for Test { 36 | type RuntimeEvent = RuntimeEvent; 37 | type WeightInfo = (); 38 | } 39 | 40 | // Build genesis storage according to the mock runtime. 41 | pub fn new_test_ext() -> sp_io::TestExternalities { 42 | GenesisConfig::::default().build_storage().unwrap().into() 43 | } -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/simple_mode/src/tests.rs.templ: -------------------------------------------------------------------------------- 1 | use crate::{mock::*, *}; 2 | use frame::deps::frame_support::{assert_noop, assert_ok}; 3 | 4 | #[test] 5 | fn it_works_for_default_value() { 6 | new_test_ext().execute_with(|| { 7 | // Dispatch a signed extrinsic. 8 | assert_ok!(^^name|capitalize^^::do_something(RuntimeOrigin::signed(1), 42)); 9 | // Read pallet storage and assert an expected result. 10 | assert_eq!(Something::::get().map(|v| v.block_number), Some(42)); 11 | }); 12 | } 13 | 14 | #[test] 15 | fn correct_error_for_none_value() { 16 | new_test_ext().execute_with(|| { 17 | // Ensure the expected error is thrown when no value is present. 18 | assert_noop!( 19 | ^^name|capitalize^^::cause_error(RuntimeOrigin::signed(1)), 20 | Error::::NoneValue 21 | ); 22 | }); 23 | } -------------------------------------------------------------------------------- /crates/pop-parachains/templates/pallet/simple_mode/src/weights.rs.templ: -------------------------------------------------------------------------------- 1 | 2 | //! Autogenerated weights for pallet_template 3 | //! 4 | //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev 5 | //! DATE: 2023-04-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` 6 | //! WORST CASE MAP SIZE: `1000000` 7 | //! HOSTNAME: `_`, CPU: `` 8 | //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 9 | 10 | // Executed Command: 11 | // ../../target/release/node-template 12 | // benchmark 13 | // pallet 14 | // --chain 15 | // dev 16 | // --pallet 17 | // pallet_template 18 | // --extrinsic 19 | // * 20 | // --steps=50 21 | // --repeat=20 22 | // --wasm-execution=compiled 23 | // --output 24 | // pallets/template/src/weights.rs 25 | // --template 26 | // ../../.maintain/frame-weight-template.hbs 27 | 28 | #![cfg_attr(rustfmt, rustfmt_skip)] 29 | #![allow(unused_parens)] 30 | #![allow(unused_imports)] 31 | 32 | use frame::deps::{frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}, frame_system}; 33 | use core::marker::PhantomData; 34 | 35 | /// Weight functions needed for pallet_template. 36 | pub trait WeightInfo { 37 | fn do_something() -> Weight; 38 | fn cause_error() -> Weight; 39 | } 40 | 41 | /// Weights for pallet_template using the Substrate node and recommended hardware. 42 | pub struct SubstrateWeight(PhantomData); 43 | impl WeightInfo for SubstrateWeight { 44 | /// Storage: TemplateModule Something (r:0 w:1) 45 | /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) 46 | fn do_something() -> Weight { 47 | // Proof Size summary in bytes: 48 | // Measured: `0` 49 | // Estimated: `0` 50 | // Minimum execution time: 8_000_000 picoseconds. 51 | Weight::from_parts(9_000_000, 0) 52 | .saturating_add(T::DbWeight::get().writes(1_u64)) 53 | } 54 | /// Storage: TemplateModule Something (r:1 w:1) 55 | /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) 56 | fn cause_error() -> Weight { 57 | // Proof Size summary in bytes: 58 | // Measured: `32` 59 | // Estimated: `1489` 60 | // Minimum execution time: 6_000_000 picoseconds. 61 | Weight::from_parts(6_000_000, 1489) 62 | .saturating_add(T::DbWeight::get().reads(1_u64)) 63 | .saturating_add(T::DbWeight::get().writes(1_u64)) 64 | } 65 | } 66 | 67 | // For backwards compatibility and tests 68 | impl WeightInfo for () { 69 | /// Storage: TemplateModule Something (r:0 w:1) 70 | /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) 71 | fn do_something() -> Weight { 72 | // Proof Size summary in bytes: 73 | // Measured: `0` 74 | // Estimated: `0` 75 | // Minimum execution time: 8_000_000 picoseconds. 76 | Weight::from_parts(9_000_000, 0) 77 | .saturating_add(RocksDbWeight::get().writes(1_u64)) 78 | } 79 | /// Storage: TemplateModule Something (r:1 w:1) 80 | /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) 81 | fn cause_error() -> Weight { 82 | // Proof Size summary in bytes: 83 | // Measured: `32` 84 | // Estimated: `1489` 85 | // Minimum execution time: 6_000_000 picoseconds. 86 | Weight::from_parts(6_000_000, 1489) 87 | .saturating_add(RocksDbWeight::get().reads(1_u64)) 88 | .saturating_add(RocksDbWeight::get().writes(1_u64)) 89 | } 90 | } -------------------------------------------------------------------------------- /crates/pop-parachains/tests/parachain.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | use anyhow::Result; 4 | use pop_parachains::up::Zombienet; 5 | use std::path::Path; 6 | 7 | const BINARY_VERSION: &str = "stable2412"; 8 | 9 | #[tokio::test] 10 | async fn launch_kusama() -> Result<()> { 11 | let temp_dir = tempfile::tempdir()?; 12 | let cache = temp_dir.path().to_path_buf(); 13 | 14 | let mut zombienet = Zombienet::new( 15 | &cache, 16 | Path::new("../../tests/networks/kusama.toml").try_into()?, 17 | Some(BINARY_VERSION), 18 | Some("v1.2.7"), 19 | None, 20 | None, 21 | None, 22 | ) 23 | .await?; 24 | 25 | for binary in zombienet.binaries().filter(|b| !b.exists()) { 26 | binary.source(true, &(), true).await?; 27 | } 28 | 29 | zombienet.spawn().await?; 30 | Ok(()) 31 | } 32 | 33 | #[tokio::test] 34 | async fn launch_paseo() -> Result<()> { 35 | let temp_dir = tempfile::tempdir()?; 36 | let cache = temp_dir.path().to_path_buf(); 37 | 38 | let mut zombienet = Zombienet::new( 39 | &cache, 40 | Path::new("../../tests/networks/paseo.toml").try_into()?, 41 | Some(BINARY_VERSION), 42 | Some("v1.2.4"), 43 | None, 44 | None, 45 | None, 46 | ) 47 | .await?; 48 | 49 | for binary in zombienet.binaries().filter(|b| !b.exists()) { 50 | binary.source(true, &(), true).await?; 51 | } 52 | 53 | zombienet.spawn().await?; 54 | Ok(()) 55 | } 56 | 57 | #[tokio::test] 58 | async fn launch_polkadot() -> Result<()> { 59 | let temp_dir = tempfile::tempdir()?; 60 | let cache = temp_dir.path().to_path_buf(); 61 | 62 | let mut zombienet = Zombienet::new( 63 | &cache, 64 | Path::new("../../tests/networks/polkadot.toml").try_into()?, 65 | Some(BINARY_VERSION), 66 | Some("v1.2.7"), 67 | None, 68 | None, 69 | None, 70 | ) 71 | .await?; 72 | 73 | for binary in zombienet.binaries().filter(|b| !b.exists()) { 74 | binary.source(true, &(), true).await?; 75 | } 76 | 77 | zombienet.spawn().await?; 78 | Ok(()) 79 | } 80 | 81 | #[tokio::test] 82 | async fn launch_polkadot_and_system_parachain() -> Result<()> { 83 | let temp_dir = tempfile::tempdir()?; 84 | let cache = temp_dir.path().to_path_buf(); 85 | 86 | let mut zombienet = Zombienet::new( 87 | &cache, 88 | Path::new("../../tests/networks/polkadot+collectives.toml").try_into()?, 89 | Some(BINARY_VERSION), 90 | Some("v1.2.7"), 91 | Some(BINARY_VERSION), 92 | Some("v1.2.7"), 93 | None, 94 | ) 95 | .await?; 96 | 97 | for binary in zombienet.binaries().filter(|b| !b.exists()) { 98 | binary.source(true, &(), true).await?; 99 | } 100 | 101 | zombienet.spawn().await?; 102 | Ok(()) 103 | } 104 | 105 | #[tokio::test] 106 | async fn launch_paseo_and_system_parachain() -> Result<()> { 107 | let temp_dir = tempfile::tempdir()?; 108 | let cache = temp_dir.path().to_path_buf(); 109 | 110 | let mut zombienet = Zombienet::new( 111 | &cache, 112 | Path::new("../../tests/networks/paseo+coretime.toml").try_into()?, 113 | Some(BINARY_VERSION), 114 | None, 115 | Some(BINARY_VERSION), 116 | Some("v1.3.3"), // 1.3.3 is where coretime-paseo-local was introduced. 117 | None, 118 | ) 119 | .await?; 120 | 121 | for binary in zombienet.binaries().filter(|b| !b.exists()) { 122 | binary.source(true, &(), true).await?; 123 | } 124 | 125 | zombienet.spawn().await?; 126 | Ok(()) 127 | } 128 | 129 | #[tokio::test] 130 | async fn launch_paseo_and_two_parachains() -> Result<()> { 131 | let temp_dir = tempfile::tempdir()?; 132 | let cache = temp_dir.path().to_path_buf(); 133 | 134 | let mut zombienet = Zombienet::new( 135 | &cache, 136 | Path::new("../../tests/networks/pop.toml").try_into()?, 137 | Some(BINARY_VERSION), 138 | None, 139 | Some(BINARY_VERSION), 140 | None, 141 | Some(&vec!["https://github.com/r0gue-io/pop-node#node-v0.3.0".to_string()]), 142 | ) 143 | .await?; 144 | 145 | for binary in zombienet.binaries().filter(|b| !b.exists()) { 146 | binary.source(true, &(), true).await?; 147 | } 148 | 149 | zombienet.spawn().await?; 150 | Ok(()) 151 | } 152 | -------------------------------------------------------------------------------- /crates/pop-telemetry/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pop-telemetry" 3 | description = "Library for collecting anonymous Usage Metrics Collection with Umami." 4 | readme = "README.md" 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | version.workspace = true 9 | 10 | [dependencies] 11 | dirs = { workspace = true } 12 | env_logger.workspace = true 13 | log.workspace = true 14 | reqwest.workspace = true 15 | serde.workspace = true 16 | serde_json.workspace = true 17 | thiserror.workspace = true 18 | tokio.workspace = true 19 | 20 | [dev-dependencies] 21 | mockito.workspace = true 22 | tempfile.workspace = true -------------------------------------------------------------------------------- /crates/pop-telemetry/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /crates/pop-telemetry/README.md: -------------------------------------------------------------------------------- 1 | # Telemetry 2 | 3 | Anonymous Usage Metrics Collection with Umami 4 | 5 | ## Umami 6 | 7 | Umami is an analytics platform that is privacy-focused and open-source. It is a great alternative to Google Analytics. 8 | We self-host Umami in the EU to safeguard all anonymous data. 9 | 10 | You can read more about Umami [here](https://umami.is/). 11 | 12 | ## Why Collect Anonymous Usage Metrics? 13 | 14 | We understand the importance of privacy and are committed to safeguarding the data of our users. Collecting 15 | anonymous usage metrics can provide invaluable insights into how our CLI tool is being utilized, allowing us to improve 16 | its performance, reliability, and user experience. Here's why we collect anonymous usage metrics: 17 | 18 | 1. **Improving User Experience**: By understanding how our CLI tool is used in real-world scenarios, we can identify 19 | areas for improvement and prioritize features that will enhance the overall user experience. 20 | 21 | 2. **Bug Identification and Resolution**: Anonymous usage metrics help us identify and prioritize bugs and issues that 22 | may not be immediately apparent. This allows us to provide quicker resolutions and ensure a smoother user experience. 23 | 24 | 3. **Feature Prioritization**: Knowing which features are used most frequently helps us prioritize development efforts 25 | and allocate resources effectively to meet the needs of our users. 26 | 27 | ## What We Collect 28 | 29 | We do **not** collect **any** personal information. We do not collect file names, GitHub repositories, or anything 30 | that is potentially sensitive. We do not even try to collect and sanitize this data, we simply do not collect it. 31 | Here is what we do collect, anonymously: 32 | 33 | 1. **Command Usage**: We collect information about the commands that are executed using our CLI tool. This includes 34 | the type of command and options used. For example, we may report that `pop new parachain` was executed with the Pop 35 | Standard template. 36 | 2. **CLI Usage**: We collect information about how often the CLI tool is used. 37 | 38 | ## Our Commitment to Privacy 39 | 40 | We take privacy seriously and are committed to protecting the anonymity of our users. Here's how we ensure your privacy: 41 | 42 | - **Anonymous Data Collection**: We only collect anonymized usage metrics, which do not contain any personally 43 | identifiable information. 44 | 45 | - **Transparency**: We are transparent about the data we collect and how it is used. Some portions of the data will be 46 | made public, 47 | such as the number of times a command was executed, and total number of users. 48 | 49 | - **Privacy-First Platform**: We use Umami, a privacy-focused and open-source analytics platform, to collect anonymous 50 | usage metrics. 51 | 52 | - **EU and GDPR Compliance**: We self-host Umami in the EU to ensure compliance with the General Data Protection 53 | Regulation (GDPR) and safeguard the data of our users. This ensures there is no 3rd party involved in the data. 54 | 55 | ## How to Opt-Out 56 | 57 | If you prefer not to participate in anonymous usage metrics collection, there are a 58 | few ways you can opt out. We support the [DO_NOT_TRACK](https://consoledonottrack.com/) and CI environment variable 59 | standards. 60 | 61 | 1. Set the `DO_NOT_TRACK` environment variable to `true` or `1` 62 | 2. Set the `CI` environment variable to `true` or `1` 63 | 3. Completely disable telemetry, by installing with telemetry compiled out: 64 | 65 | ```bash 66 | cargo install --locked --no-default-features --features contract,parachain --git "https://github.com/r0gue-io/pop-cli" 67 | ``` 68 | 69 | ## Questions or Concerns? 70 | 71 | If you have any questions or concerns regarding our telemetry practices, please don't 72 | hesitate to contact us: 73 | 74 | - Contact form: [r0gue.io/contact](https://r0gue.io/contact) 75 | - Telegram: [Pop](https://t.me/onpopio) 76 | 77 | Thank you for your support and understanding. 78 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | all-features = false 3 | 4 | # This section is considered when running `cargo deny check advisories` 5 | [advisories] 6 | ignore = [ 7 | { id = "RUSTSEC-2024-0344", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/214" }, 8 | { id = "RUSTSEC-2024-0388", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/436" }, 9 | { id = "RUSTSEC-2024-0384", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/437" }, 10 | { id = "RUSTSEC-2020-0163", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/438" }, 11 | { id = "RUSTSEC-2024-0436", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/450" }, 12 | { id = "RUSTSEC-2025-0012", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/451" }, 13 | { id = "RUSTSEC-2024-0370", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/458" }, 14 | { id = "RUSTSEC-2022-0061", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/458" }, 15 | { id = "RUSTSEC-2020-0168", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/458" }, 16 | { id = "RUSTSEC-2024-0438", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/458" }, 17 | { id = "RUSTSEC-2023-0091", reason = "No upgrade available. Tracking the vulnerability: https://github.com/r0gue-io/pop-cli/issues/458" }, 18 | ] 19 | 20 | [licenses] 21 | allow = [ 22 | "Apache-2.0", 23 | "Apache-2.0 WITH LLVM-exception", 24 | "BSL-1.0", 25 | "BSD-2-Clause", 26 | "BSD-3-Clause", 27 | "CC0-1.0", 28 | "ISC", 29 | "GPL-3.0", 30 | "GPL-3.0 WITH Classpath-exception-2.0", # For Substrate crates 31 | "MIT", 32 | "MPL-2.0", 33 | "Unicode-3.0", 34 | "Unicode-DFS-2016", 35 | "Unlicense", 36 | "Zlib" 37 | ] 38 | confidence-threshold = 0.93 39 | 40 | [[licenses.exceptions]] 41 | allow = ["OpenSSL"] 42 | name = "ring" 43 | 44 | [[licenses.clarify]] 45 | crate = "webpki" 46 | expression = "ISC" 47 | license-files = [ 48 | { path = "LICENSE", hash = 0x001c7e6c }, 49 | ] 50 | 51 | [[licenses.exceptions]] 52 | allow = ["GPL-3.0-or-later WITH Classpath-exception-2.0"] 53 | name = "cumulus-rely-chain-interface" 54 | 55 | [[licenses.clarify]] 56 | name = "ring" 57 | expression = "ISC AND MIT AND OpenSSL" 58 | license-files = [ 59 | { path = "LICENSE", hash = 0xbd0eed23 }, 60 | ] 61 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.86" 3 | components = [ 4 | "cargo", 5 | "clippy", 6 | "rust-analyzer", 7 | "rust-src", 8 | "rust-std", 9 | "rustc", 10 | "rustfmt", 11 | ] 12 | targets = ["wasm32-unknown-unknown"] 13 | profile = "minimal" 14 | -------------------------------------------------------------------------------- /tests/networks/kusama+asset-hub.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/kusama+asset-hub.toml 2 | 3 | [relaychain] 4 | chain = "kusama-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1000 16 | chain = "asset-hub-kusama-local" 17 | 18 | [[parachains.collators]] 19 | name = "asset-hub" -------------------------------------------------------------------------------- /tests/networks/kusama.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/kusama.toml 2 | 3 | [relaychain] 4 | chain = "kusama-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true -------------------------------------------------------------------------------- /tests/networks/paseo+asset-hub.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/paseo+asset-hub.toml -S v1.3.4 2 | 3 | [relaychain] 4 | chain = "paseo-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1000 16 | chain = "asset-hub-paseo-local" 17 | 18 | [[parachains.collators]] 19 | name = "asset-hub" 20 | args = ["-lxcm=trace,lsystem::events=trace,lruntime=trace"] -------------------------------------------------------------------------------- /tests/networks/paseo+bridge-hub.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/paseo+bridge-hub.toml 2 | 3 | [relaychain] 4 | chain = "paseo-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1000 16 | chain = "bridge-hub-paseo-local" 17 | 18 | [[parachains.collators]] 19 | name = "bridge-hub" 20 | args = ["-lxcm=trace,lsystem::events=trace,lruntime=trace"] -------------------------------------------------------------------------------- /tests/networks/paseo+collectives.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/paseo+collectives.toml 2 | 3 | [relaychain] 4 | chain = "paseo-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1004 16 | chain = "collectives-paseo-local" 17 | 18 | [[parachains.collators]] 19 | name = "collectives" 20 | args = ["-lxcm=trace,lsystem::events=trace,lruntime=trace"] 21 | -------------------------------------------------------------------------------- /tests/networks/paseo+coretime.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/paseo+coretime.toml 2 | 3 | [relaychain] 4 | chain = "paseo-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1005 16 | chain = "coretime-paseo-local" 17 | 18 | [[parachains.collators]] 19 | name = "coretime" 20 | args = ["-lxcm=trace,lsystem::events=trace,lruntime=trace"] -------------------------------------------------------------------------------- /tests/networks/paseo+people.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/paseo+people.toml 2 | 3 | [relaychain] 4 | chain = "paseo-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1004 16 | chain = "people-paseo-local" 17 | 18 | [[parachains.collators]] 19 | name = "people" 20 | args = ["-lxcm=trace,lsystem::events=trace,lruntime=trace"] 21 | -------------------------------------------------------------------------------- /tests/networks/paseo.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/paseo.toml 2 | 3 | [relaychain] 4 | chain = "paseo-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true -------------------------------------------------------------------------------- /tests/networks/polkadot+asset-hub.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/polkadot+asset-hub.toml 2 | 3 | [relaychain] 4 | chain = "polkadot-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1000 16 | chain = "asset-hub-polkadot-local" 17 | 18 | [[parachains.collators]] 19 | name = "asset-hub" -------------------------------------------------------------------------------- /tests/networks/polkadot+collectives.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/polkadot.toml 2 | 3 | [relaychain] 4 | chain = "polkadot-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1000 16 | chain = "collectives-polkadot-local" 17 | 18 | [[parachains.collators]] 19 | name = "collectives-01" -------------------------------------------------------------------------------- /tests/networks/polkadot.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/polkadot.toml 2 | 3 | [relaychain] 4 | chain = "polkadot-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true -------------------------------------------------------------------------------- /tests/networks/pop.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/pop.toml 2 | 3 | [relaychain] 4 | chain = "paseo-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1000 16 | chain = "asset-hub-paseo-local" 17 | 18 | [[parachains.collators]] 19 | name = "asset-hub" 20 | 21 | [[parachains]] 22 | id = 4385 23 | default_command = "pop-node" 24 | 25 | [[parachains.collators]] 26 | name = "pop" 27 | args = ["-lruntime::contracts=debug"] 28 | 29 | [[hrmp_channels]] 30 | sender = 1000 31 | recipient = 4385 32 | max_capacity = 1000 33 | max_message_size = 5000 34 | 35 | [[hrmp_channels]] 36 | sender = 4385 37 | recipient = 1000 38 | max_capacity = 1000 39 | max_message_size = 8000 -------------------------------------------------------------------------------- /tests/networks/template.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/template.toml 2 | 3 | [relaychain] 4 | chain = "paseo-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 2000 16 | default_command = "./target/release/parachain-template-node" 17 | 18 | [[parachains.collators]] 19 | name = "collator-01" -------------------------------------------------------------------------------- /tests/networks/westend+asset-hub.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/westend+asset-hub.toml 2 | 3 | [relaychain] 4 | chain = "westend-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | 14 | [[parachains]] 15 | id = 1000 16 | chain = "asset-hub-westend-local" 17 | 18 | [[parachains.collators]] 19 | name = "asset-hub" -------------------------------------------------------------------------------- /tests/networks/westend.toml: -------------------------------------------------------------------------------- 1 | # pop up network ./tests/networks/westend.toml 2 | 3 | [relaychain] 4 | chain = "westend-local" 5 | 6 | [[relaychain.nodes]] 7 | name = "alice" 8 | validator = true 9 | 10 | [[relaychain.nodes]] 11 | name = "bob" 12 | validator = true 13 | -------------------------------------------------------------------------------- /tests/runtimes/base_parachain.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r0gue-io/pop-cli/92b9d02f8c1ce78f3d2b47b77f1b15d09a2fe6c8/tests/runtimes/base_parachain.wasm -------------------------------------------------------------------------------- /tests/runtimes/base_parachain_benchmark.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r0gue-io/pop-cli/92b9d02f8c1ce78f3d2b47b77f1b15d09a2fe6c8/tests/runtimes/base_parachain_benchmark.wasm -------------------------------------------------------------------------------- /tests/runtimes/base_parachain_try_runtime.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r0gue-io/pop-cli/92b9d02f8c1ce78f3d2b47b77f1b15d09a2fe6c8/tests/runtimes/base_parachain_try_runtime.wasm -------------------------------------------------------------------------------- /tests/snapshots/base_parachain.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r0gue-io/pop-cli/92b9d02f8c1ce78f3d2b47b77f1b15d09a2fe6c8/tests/snapshots/base_parachain.snap --------------------------------------------------------------------------------