Skip to content
/ XCMTemplate Public template

Building Cross-chain (XCM) DApps with Docker

License

Notifications You must be signed in to change notification settings

ltfschoen/XCMTemplate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Smart Contract in ink!

Table of Contents

Setup

Setup Docker Container

  • Note:

    • The docker/Dockerfile and its dependencies in docker/utility are modified copies of files in https://github.com/paritytech/scripts/blob/master/dockerfiles to have the flexibility to develop locally using ink! in a Docker container that uses Linux without any dependency issues, and where changes replicated on the host machine.
    • This is useful if you're on an old macOS Catalina and Apple won't allow you to do further software updates, so you cannot install brew install protobuf to install necessary dependencies.
  • Install and run Docker

  • Generate .env file from sample file

    • Generate a Substrate-based account on an air-gapped machine using Subkey or by installing Subkey in Docker on an air-gapped machine
    • Add the mnemonic phrase of the Substrate-based account to the value of LS_CONTRACTS in the .env file.
    • Obtain testnet tokens from faucet at https://use.ink/faucet/
  • Check versions used in Dockerfile:

    • Rust nightly version
    • Node.js version
    • Cargo Contract
    • Substrate Contracts Node
  • Configure amount of CPUs and memory of the host machine the Docker container should use in ./docker/docker.sh. Use docker update to change the configuration https://docs.docker.com/engine/reference/commandline/container_update/

  • Update dependencies in ./dapps/ink-rust/wasm-flipper/package.json https://stackoverflow.com/a/70588930/3208553

     cd ./dapps/ink-rust/wasm-flipper/
     yarn upgrade
    
  • Update dependencies in ./dapps/ink-rust/wasm-flipper/contract/flipper/Cargo.toml

     cd ./dapps/ink-rust/wasm-flipper/contract/flipper/
     cargo update
    
  • Run Docker container and follow the terminal log instructions.

    • Note: Optionally exclude installing substrate-contracts-node by running time ./docker/docker.sh "without_node" since including it will increase build time substantially and may not be necessary if you are deploying to remote testnets
     touch .env && cp .env.example .env
     time ./docker/docker.sh
  • Check Memory & CPU usage. Update memory limits in docker-compose.yml

     docker stats
  • Enter Docker container

     docker exec -it ink /bin/bash
  • Optional Attach to Container in Visual Studio Code (VSCode)

    • Open folder /app with cd /app
  • Check versions. Note:

rustup toolchain list
rustup update
rustup show
cargo-contract --version
  • Check if substrate-contracts-node was installed if you used the "with-node" argument
substrate-contracts-node --version

Run Cargo Contracts Node in Docker Container

  • Important This is only available if you did not run ./docker/run.sh using "without_node" argument

Run Node

  • Run Cargo Contract Node
    • Note: Use either --tmp or --base-path "/tmp/ink"
    • Note: Delete chain database rm -rf /tmp/ink.
    • Note: Check disk space used by database du /tmp/ink
  • Note: Refer to debugging docs https://use.ink/basics/contract-debugging
./docker/run-scn.sh
  • Leave that terminal tab running the node. Enter the terminal again in a new tab with docker exec -it ink /bin/bash

  • Attach to the running terminal with VSCode if necessary. See here

  • Restart the node and delete the chain database by running ./docker/reset.sh inside the Docker container or docker exec -it ink /app/docker/reset.sh from outside the Docker container and waiting 15 seconds

Interact with Node

Demo Quickstart Build & Upload ink! Rust Flipper Smart Contract to Local Testnet (using Cargo Contract)

Option 1: Run from host machine

SCN_PORT=$(docker exec -it ink lsof -ti:30333) && \
docker exec -it ink echo $(kill -9 $SCN_PORT) && \
docker exec -it ink /app/docker/quickstart.sh

Option 2: Run from shell inside Docker container

* Enter shell of Docker container
	```bash
	docker exec -it ink /bin/bash
	```
* Run quickstart
	```bash
	./docker/quickstart.sh
	```
  • Note: This may be run repeatedly since it automatically:
    • Kills any existing substrate-contracts-node on port 30333
    • Empties the chain database with rm -rf /tmp/ink so we can redeploy the Flipper contract to the same address
    • Run substrate-contracts-node again
    • Redeploys the Flipper contract
    • Interacts with the Flipper contract

Build & Upload ink! Rust Flipper Smart Contract to Local Testnet (using Cargo Contract)

  • Create Rust project with template
cd dapps/ink-rust/wasm-flipper/contract
cargo contract new flipper
cd flipper
  • Optionally build with VSCode by adding the project "dapps/ink-rust/wasm-flipper/contract/flipper" to the list of members in the Cargo.toml file in the project root, and running "Terminal > Run Task > Build Contract" to build all the contract using the configuration in ./.vscode/launch.json

  • Generate .contract, .wasm, and metadata.json code. Note: Use --release to deploy in "release" mode (without debug logs) instead of "debug" mode

    • Note: If you get error ERROR: Cannot read /app/target/ink/flipper/.target then run rm -rf /app/target
     cargo contract build --manifest-path /app/dapps/ink-rust/wasm-flipper/contract/flipper/Cargo.toml
  • Copy ./target/ink/flipper/flipper.json

    • Paste this as the ABI value of const abi = ./dapps/ink-rust/wasm-flipper/ui/components/abi.ts
    • Note: Refer to ./docker/quickstart.sh that shows how to do this programmatically
  • Upload Contract (note: prefer to use contracts-ui to avoid exposing private key)

cargo contract upload --suri //Alice
  • Actual output:

    • Terminal #1

       Result Success!
       Code hash "0xec3a66a8f99674ecf25d180fc39ee8e620d45e8de459277e353ece20753d6c53"
       	Deposit 434925000000
       Your upload call has not been executed.
       To submit the transaction and execute the call on chain, add -x/--execute flag to the command
      
    • Terminal #2

       2023-05-11 05:49:47.389  INFO tokio-runtime-worker substrate: 💤 Idle (0 peers), best: #0 (0x18c5…59af), finalized #0 (0x18c5…59af), ⬇ 0 ⬆ 0    
       2023-05-11 05:49:48.124 DEBUG tokio-runtime-worker sync: Propagating transactions    
       2023-05-11 05:49:48.437  INFO tokio-runtime-worker jsonrpsee_ws_server::server: Accepting new connection 1/100
      
  • Upload and Execute it. Optionally --skip-dry-run

cargo contract upload --suri //Alice \
	--execute \
	--skip-confirm
  • Copy the "Code hash" that is output since you can query the contract by pasting it at https://contracts-ui.substrate.io/hash-lookup?rpc=ws://127.0.0.1:9944 and pasting its ABI as the Metadata using the contents of ./target/ink/flipper/flipper.json

  • Note: The output format should be:

     CodeStored event
     code_hash: 0x......
    
    • Note: only one copy of the code is stored, but there can be many instance of one code blob, differs from other EVM chains where each node has a copy
  • Actual output:

    • Terminal #1

       Events
       	...
      
       Code hash "0xec3a66a8f99674ecf25d180fc39ee8e620d45e8de459277e353ece20753d6c53"
      
    • Terminal #2

      
       2023-05-11 05:56:14.102  INFO tokio-runtime-worker substrate: ✨ Imported #1 (0x9bc4…be22)    
       2023-05-11 05:56:14.699 DEBUG tokio-runtime-worker sync: Propagating transactions    
       2023-05-11 05:56:15.591  INFO tokio-runtime-worker substrate: 💤 Idle (0 peers), best: #1 (0x9bc4…be22), finalized #0 (0x18c5…59af), ⬇ 0 ⬆ 0
      

Build & Upload ink! Rust Flipper Smart Contract to Local Testnet (using Swanky CLI)

Install Swanky CLI https://github.com/AstarNetwork/swanky-cli

cd dapps/ink-rust/wasm-flipper
nvm use
yarn global add @astar-network/swanky-cli@2.1.2
  1. Init
cd contract
swanky init flipper

Note: Choose ink as a contract language and flipper as template and a chosen contract name. Optionally choose from Y/N when asking to download the Swanky Node (NOT required if already using Substrate Contracts Node).

  1. Start the local node

If you chose to download the Swanky Node then run it in your local environment:

cd flipper
swanky node start
  1. Build the contract

Build the contract in a new tab

swanky contract compile flipper

Note: Try rustup update if you face error

  1. Deploy the contract

Local Testnet

swanky contract deploy flipper --account alice -g 100000000000 -a true

Shibuya Testnet

swanky contract deploy flipper --account alice --gas 100000000000 --args true --network shibuya

Copy paste the contract address.

  1. Update WS_PROVIDER to check if it connects to Shibuya or localhost in ./dapps/ink-rust/wasm-flipper/ui/components/app.tsx

  2. View in block explorer if deploy on Astar https://astar.subscan.io/wasm_contract_dashboard?tab=contract

Interact with ink! Python Smart Contract

  • Run the steps in the Setup section (if you want to connect to a local node)
  • Enter the Docker container with docker exec -it ink /bin/bash
  • Run the following inside the Docker container
cd ./dapps/ink-python/example
pip3 install --no-cache-dir -r requirements.txt
python3 ./src/app.py
  • Note: If you get error DuplicateContract', 'docs': ['A contract with the same AccountId already exists then restart the substrate-contracts-node and reset the database by simply running the following on the host machine from outside the Docker container and waiting for approx. 15 seconds before running your commands again. Or run /app/docker/reset.sh from within the Docker container.

     docker exec -it ink /app/docker/reset.sh
  • Note: If you get error ValueError: Invalid mnemonic: invalid word in phrase then you needed to set account mnemonic phrase as the value of LS_CONTRACT in the .env file and obtain testnet tokens for it from faucet at https://use.ink/faucet/

Interact with ink! Rust Flipper Smart Contract using Polkadot.js API

Interact with ink! Rust Flipper Smart Contract using Substrate Contracts Node

Cargo Contracts

  • Instantiate Contract
cargo contract instantiate \
	--suri //Bob \
	--constructor new \
	--args true \
	--execute \
	--skip-confirm
  • Wait for response
...
Event System => NewAccount
	account: 5G...
...
Event Contracts + Instantiated
	deployer: 5F...
	contract: 5G.... (new contract account address to interact with contract)
...
  • Store the response in an environment variable for reuse. Replace the example value below of 5G.... with the actual contract account address provided in the event response above.
CONTRACT_ADDR=5G....
echo "stored in variable CONTRACT_ADDR the contract address value ${CONTRACT_ADDR" 
  • Interact to flip the boolean value, not a dry run so no response but we get a gas limit response
cargo contract call \
	--suri //Charlie \
	--contract $CONTRACT_ADDR \
	--message flip \
	--execute \
	--skip-confirm
  • Check it flipped the boolean value (dry run only)
cargo contract call \
	--suri //Charlie \
	--contract $CONTRACT_ADDR \
	--message get \
	--execute \
	--skip-confirm
  • Check the outputs:
    • Emitted events in the terminal where you run cargo contract ... comments
    • Debug logs in the substrate-contracts-node terminal
    • Optionally go to https://contracts-ui.substrate.io/hash-lookup?rpc=ws://127.0.0.1:9944 and paste the "Code hash" from when you initially uploaded the contract, and pasting its ABI as the Metadata using the contents of ./target/ink/flipper/flipper.json
  • Note: If you don't build in "debug" mode with cargo contract build ... instead of cargo contract build --release ... and you run it using dry run by running extra options like the following, or if you execute as a transaction, then you won't be able to see node terminal debug logs like tokio-runtime-worker runtime::contracts Execution finished with debug buffer... from your use of ink::env::debug_println! in the smart contract
	--skip-dry-run \
	--gas 100000000000 \
	--proof-size 100000000000

Tips Docker Commands

  • List Docker containers
docker ps -a
  • List Docker images
docker images -a
docker buildx ls
  • Enter Docker container shell
docker exec -it $CONTAINER_ID /bin/sh
  • View Docker container logs
docker logs -f $CONTAINER_ID
  • Remove Docker container
docker stop $CONTAINER_ID; docker rm $CONTAINER_ID;
  • Remove Docker image
docker rmi $IMAGE_ID
docker buildx rm --all-inactive
  • Reduce space used by Docker Desktop
    • Docker Preferences -> Resources -> Advanced -> Virtual Disk Limit
      • e.g. 64Gb reduce to 32Gb
      • Note: This deletes all Docker images similar to docker system prune -a --volumes

Notes

  • Strategy:

    • Why use smart contract instead of blockchain?
      • Faster iterations of design, development, testing, and release of applications to market
      • Provide core functionality for the base layer of a general purpose blockchain that is being built
      • Allow smart contracts to interact with an application-specific blockchain pallet logic and use them to expose some logic to users since smart contracts treat all user input as untrusted and potentially adversarial
      • Example:
        • Building an application where most logic in Substrate pallets
          • Allow users to upload their own trading algorithms using smart contracts
          • Smart contracts require gas fees to execute so users would pay for the execution time of those trading algorithms
          • Expose relevant primitives similar to the Chain extension primitive of the Contracts pallet
    • What types of smart contracts may be deployed on Substrate runtime?
      • WebAssembly
      • EVM-compatible
    • What is a smart contract?
      • Instructions that are instantiated and executed on a host platform using a specific smart contract chain account address
      • Instructions written in a language
    • What Substrate pallet is best to use when building a runtime to host smart contracts that are being built?
      • Contracts pallet allows deployment and execution of WebAssembly-based smart contracts
    • What trait do smart contract accounts used in the Contracts pallet of Substrate extend?
      • Currency trait
    • How to resolve ERROR: This contract has already been uploaded with code hash
      • It may be because you ran a Substrate contract node on your host machine and then tried running another one in your Docker container. So it may be necessary to run kill -9 $(lsof -ti:30333) on both the host machine and inside the Docker container. Or just restart Docker.
  • Link

TODO - continue summarising from "Smart contract accounts" section

Links

Ink

Docker