mirror of
https://github.com/pezkuwichain/pezkuwi-runtime-templates.git
synced 2026-04-21 23:47:56 +00:00
317 lines
9.2 KiB
Plaintext
317 lines
9.2 KiB
Plaintext
:source-highlighter: highlight.js
|
||
:highlightjs-languages: rust
|
||
:github-icon: pass:[<svg class="icon"><use href="#github-icon"/></svg>]
|
||
|
||
= Testing Solidity Smart Contracts with Zombienet
|
||
|
||
In this tutorial, we will demonstrate how to deploy your teyrchain using Zombienet, and test the functionalities of your teyrchain.
|
||
|
||
Below are the main steps of this demo:
|
||
. Deploy our teyrchain against the locally simulated Paseo testnet by Zombienet.
|
||
. Deploy a Solidity smart contract on our teyrchain.
|
||
. Successfully invoke the Solidity smart contract deployed on our teyrchain.
|
||
|
||
== Step 1: Deploy The Teyrchain
|
||
|
||
. git clone https://github.com/OpenZeppelin/pezkuwi-runtime-templates
|
||
. move to evm template directory
|
||
|
||
+
|
||
```bash
|
||
cd evm-template
|
||
```
|
||
|
||
. replace the content of `zombinet-config/devnet.toml` with:
|
||
+
|
||
```rust
|
||
[relaychain]
|
||
chain = "paseo-local"
|
||
default_command = "./bin-v1.6.0/pezkuwi"
|
||
|
||
[[relaychain.nodes]]
|
||
name = "alice"
|
||
validator = true
|
||
|
||
[[relaychain.nodes]]
|
||
name = "bob"
|
||
validator = true
|
||
|
||
[relaychain.genesis.runtimeGenesis.patch.configuration.config]
|
||
scheduling_lookahead = 2
|
||
|
||
[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params]
|
||
max_candidate_depth = 3
|
||
allowed_ancestry_len = 2
|
||
```
|
||
|
||
. build the zombienet:
|
||
+
|
||
|
||
```bash
|
||
./scripts/zombienet.sh build
|
||
```
|
||
+
|
||
.if you came across this error (click to expand):
|
||
[%collapsible]
|
||
====
|
||
```bash
|
||
error[E0635]: unknown feature `stdsimd`
|
||
--> /Users/ozgunozerk/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ahash-0.7.6/src/lib.rs:33:42
|
||
|
|
||
33 | #![cfg_attr(feature = "stdsimd", feature(stdsimd))]
|
||
```
|
||
|
||
`Cd` into the `pezkuwi-sdk` directory (the path should be visible on the error message), and run the below command to update `ahash`:
|
||
|
||
```bash
|
||
cargo update -p ahash@0.7.6
|
||
```
|
||
====
|
||
+
|
||
.or if you came across this error (click to expand):
|
||
[%collapsible]
|
||
====
|
||
|
||
```rust
|
||
assertion failed [block != nullptr]: BasicBlock requested for unrecognized address
|
||
```
|
||
|
||
just re-run 🙂
|
||
====
|
||
|
||
. run the zombinet:
|
||
+
|
||
```bash
|
||
./scripts/zombienet.sh devnet
|
||
```
|
||
|
||
. build it with `async-backing` feature:
|
||
+
|
||
```bash
|
||
cargo build --release --features="async-backing"
|
||
```
|
||
|
||
. copy the `Direct Link` from `Alice's` tab from Zombienet TUI:
|
||
+
|
||
image::alice-direct-link.png[Alice Direct Link]
|
||
|
||
. Open the link with Chrome: link:https://pezkuwi.js.org/apps[PezkuwiJS]. As of 2024 July, it doesn’t work on Safari and Brave, and it is buggy on Firefox.
|
||
. Reserve a `ParaId` on Zombienet:
|
||
.. Go to `Network` > `Teyrchains`
|
||
.. Go to `Parathreads` tab
|
||
.. Click the `+ ParaId` button
|
||
.. Save a `teyrchain id` for the further usage.
|
||
.. Click `Submit` and `Sign and Submit` (you can use `Alice` as the account).
|
||
. Preparing necessary files to become a Teyrchain:
|
||
.. Generate a plain chainspec:
|
||
+
|
||
```bash
|
||
./target/release/evm-template-node build-spec --disable-default-bootnode > plain-teyrchain-chainspec.json
|
||
```
|
||
|
||
.. Edit the chainspec:
|
||
... Update `name`, `id` and `protocolId` to unique values (optional).
|
||
... Change `para_id` and `teyrchainInfo.teyrchainId` from `1000` to the previously saved teyrchain id (probably 2000 if that’s your first time ;) ).
|
||
... Edit the `evmChainId.chainId` to the value of your choice. Try to select a value that has no existing EVM chain assigned to it (should be ok to leave as is for the most cases).
|
||
.. Generate a raw chainspec:
|
||
+
|
||
```bash
|
||
./target/release/evm-template-node build-spec --chain plain-teyrchain-chainspec.json --disable-default-bootnode --raw > raw-teyrchain-chainspec.json
|
||
```
|
||
|
||
.. Generate the genesis state:
|
||
+
|
||
```bash
|
||
./target/release/evm-template-node export-genesis-state --chain raw-teyrchain-chainspec.json > genesis-state
|
||
```
|
||
|
||
.. Generate the validation code:
|
||
+
|
||
```bash
|
||
./target/release/evm-template-node export-genesis-wasm --chain raw-teyrchain-chainspec.json > genesis-wasm
|
||
```
|
||
|
||
. Registering the Teyrchain:
|
||
.. Go back to `pezkuwi.js.org/apps` (remember Chrome). Go to `Developer/Sudo`.
|
||
.. select `pasasSudoWrapper` and `sudoScheduleParaInitialize(id, genesis)`
|
||
.. enter the reserved id (2000) to `id` field
|
||
.. enable `file upload` for both `genesisHead` and `validationCode` → because we will upload files for these.
|
||
.. select `Yes` for `paraKind` → meaning when we register our teyrchain, it will be a teyrchain rather than a parathread.
|
||
.. drag&drop your `genesis-state` file generated in step `10.d` into `genesisHead` field (good luck with the aiming)
|
||
.. drag&drop your `genesis-wasm` file generated in `10.e` into `validationCode` field
|
||
.. It should look like below:
|
||
+
|
||
image::register-teyrchain.png[Register Teyrchain]
|
||
|
||
.. `Submit Sudo`!
|
||
. copy the path to `chain-spec` from zombienet terminal from `Bob` (beware, this file is changing every time you spin up a new zombienet):
|
||
+
|
||
image::zombie-chain-spec.png[Zombie Chain Spec]
|
||
|
||
. run the node, and provide the `chain-spec` you copied from the last step into `--chain` part:
|
||
* be sure to clear your storage if you were running a node before
|
||
+
|
||
```rust
|
||
./target/release/evm-template-node \
|
||
--alice \
|
||
--collator \
|
||
--force-authoring \
|
||
--chain raw-teyrchain-chainspec.json \
|
||
--base-path storage/alice \
|
||
--port 40333 \
|
||
--rpc-port 8844 \
|
||
-- \
|
||
--execution wasm \
|
||
--chain /var/folders/...{redacted}.../paseo-local.json \
|
||
--port 30343 \
|
||
--rpc-port 9977
|
||
```
|
||
|
||
. your node should be running without any problem, and should see block production in your node terminal!
|
||
+
|
||
image::node-success.png[Node Success]
|
||
|
||
|
||
== Step 2: Deploy a Solidity Smart Contract
|
||
|
||
. Install Foundry with: `curl -L [https://foundry.paradigm.xyz](https://foundry.paradigm.xyz/) | bash`
|
||
. have a smart contract file ready, any smart contract of your choice! We will go with a cute `HelloWorld.sol` smart contract for this tutorial:
|
||
+
|
||
```solidity
|
||
// SPDX-License-Identifier: MIT
|
||
pragma solidity ^0.8.0;
|
||
|
||
contract HelloWorld {
|
||
string public greeting = "Hello, World!";
|
||
|
||
function getGreeting() public view returns (string memory) {
|
||
return greeting;
|
||
}
|
||
}
|
||
```
|
||
|
||
. Create a new javascript project with the below files:
|
||
.. `package.json`:
|
||
+
|
||
```solidity
|
||
{
|
||
"name": "ts-wallet",
|
||
"version": "1.0.0",
|
||
"description": "",
|
||
"main": "index.js",
|
||
"type": "module",
|
||
"scripts": {
|
||
"exec": "node index.js",
|
||
"test": "echo \"Error: no test specified\" && exit 1"
|
||
},
|
||
"author": "",
|
||
"license": "ISC",
|
||
"dependencies": {
|
||
"web3": "^4.8.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
.. `sanity_check.js`:
|
||
+
|
||
```solidity
|
||
import { Web3 } from "web3";
|
||
|
||
const web3 = new Web3("ws://127.0.0.1:8844");
|
||
|
||
console.log("Balance:");
|
||
// this is the address of `Alith` in our chainspec
|
||
web3.eth.getBalance("0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac").then(console.log);
|
||
|
||
let raw = await web3.eth.accounts.signTransaction({
|
||
gas: 21000,
|
||
gasPrice: 10000000000,
|
||
from: "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac", // Alith's address
|
||
to: "0x7c98a1801f0B28dF559bCd828fc67Bd6ab558074", // Baltathar's address
|
||
value: '100000000000000000'
|
||
}, "0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133"); // Alith's private key
|
||
|
||
let res = await web3.eth.sendSignedTransaction(raw.rawTransaction);
|
||
console.log("Transaction details:");
|
||
console.log(res);
|
||
```
|
||
|
||
.. `invoke_smart_contract.js`:
|
||
+
|
||
```solidity
|
||
import { Web3 } from "web3";
|
||
import { MyAbi } from "./abi.js";
|
||
|
||
const web3 = new Web3("ws://127.0.0.1:8844");
|
||
|
||
let contract = new web3.eth.Contract(MyAbi, "0x4045F03B68919da2c440F023Fd7cE2982BfD3C03");
|
||
let s = await contract.methods.getGreeting().call();
|
||
|
||
console.log(s);
|
||
```
|
||
|
||
.. `abi.js`:
|
||
+
|
||
```solidity
|
||
export var MyAbi = [
|
||
{
|
||
"type": "function",
|
||
"name": "getGreeting",
|
||
"inputs": [],
|
||
"outputs": [
|
||
{
|
||
"name": "",
|
||
"type": "string",
|
||
"internalType": "string"
|
||
}
|
||
],
|
||
"stateMutability": "view"
|
||
},
|
||
{
|
||
"type": "function",
|
||
"name": "greeting",
|
||
"inputs": [],
|
||
"outputs": [
|
||
{
|
||
"name": "",
|
||
"type": "string",
|
||
"internalType": "string"
|
||
}
|
||
],
|
||
"stateMutability": "view"
|
||
}
|
||
];
|
||
```
|
||
|
||
. run the below command, and you should see the balance, and then the transaction details printed, proving everything works so far!
|
||
+
|
||
```solidity
|
||
node sanity_check.js
|
||
```
|
||
|
||
. open a terminal instance where the current directory has the `HelloWorld.sol` file, and run the below command to deploy the contract with Alith's private key:
|
||
+
|
||
```solidity
|
||
forge create --rpc-url http://localhost:9933 --private-key 0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133 HelloWorld.sol:HelloWorld
|
||
```
|
||
+
|
||
* don’t forget to copy the address this contract deployed to (shown in the output of the command)!
|
||
|
||
|
||
|
||
== Step 3: Invoke The Solidity Smart Contract
|
||
|
||
. replace the contract address in `invoke_smart_contract.js` with the address you copied!
|
||
. build the `abi` of the smart contract with:
|
||
+
|
||
```solidity
|
||
forge build --silent && jq '.abi' ./out/HelloWorld.sol/HelloWorld.json
|
||
```
|
||
|
||
. Surprise! We already give you the abi of this in `abi.js` file in step `3`. If you used another contract than `HelloWorld`, replace that `abi.js` file’s content with your contracts `abi`.
|
||
. run the below command, and you should see your smart contract in action:
|
||
+
|
||
```solidity
|
||
node invoke_smart_contract.js
|
||
```
|