# 0. Setup ## 1. Build ### 1.1 Docker image ```sh git clone --recursive <git url> cd firovm git checkout testnet-poa docker build -t firovm . ``` ### 1.2. Update config Some configuration need to be updated for PoA ***firovm.conf*** ``` rpcuser=<e.g. `test`> rpcpassword=<Securely random> addrindex=1 chain=test txindex=1 noconnect=1 logevents=1 readdusedcoin=1 # For auto readding used coins [test] bind=0.0.0.0 # allow connection to docker containers rpcbind=0.0.0.0 # allow connection to docker containers debug=1 logevents=1 listenonion=0 minimumchainwork=0 connect=<ip1> # ip of other nodes connect=<ip2> # ``` ## 2. Start FiroVM ### Start a docker container ```sh! docker run -v <firovm.conf path>:/firovm/firovm.conf -p 13889:13889 --name node1 firovm ``` ### Run many nodes using docker compose Example files ***docker-compose.yaml*** ```yaml! version: "3.8" services: node1: image: firovm:latest volumes: - ./firovm1.conf:/firovm/firovm.conf node2: image: firovm volumes: - ./firovm2.conf:/firovm/firovm.conf node3: image: firovm volumes: - ./firovm3.conf:/firovm/firovm.conf ``` ***firovm1.conf*** ``` rpcuser=test rpcpassword=<random this> chain=test addrindex=1 txindex=1 noconnect=1 logevents=1 readdusedcoin=1 [test] bind=0.0.0.0 rpcbind=0.0.0.0 rpcallowip=0.0.0.0/0 debug=1 logevents=1 listenonion=0 readdusedcoin=true readdusedcoin=1 minimumchainwork=0 connect=node2 connect=node3 ``` ***firovm2.conf*** ```yaml! rpcuser=test rpcpassword=<random this> chain=test addrindex=1 txindex=1 noconnect=1 logevents=1 readdusedcoin=1 [test] bind=0.0.0.0 rpcbind=0.0.0.0 rpcallowip=0.0.0.0/0 debug=1 logevents=1 listenonion=0 readdusedcoin=true readdusedcoin=1 minimumchainwork=0 connect=node1 connect=node3 ``` ***firovm3.conf*** ```yaml! rpcuser=test rpcpassword=<random this> chain=test addrindex=1 txindex=1 noconnect=1 logevents=1 readdusedcoin=1 [test] bind=0.0.0.0 rpcbind=0.0.0.0 rpcallowip=0.0.0.0/0 debug=1 logevents=1 listenonion=0 readdusedcoin=true readdusedcoin=1 minimumchainwork=0 connect=node1 connect=node2 ``` Then start docker-compose ```sh! docker compose up -d # use firovm-cli docker compose exec node1 firovm-cli <command> [<args>] # print log docker compose logs -f --tail 100 node1 ``` ## 3. Setup commands ```shell # set up CLI to call firovm node # e.g. CLI="./src/firovm-cli" ``` ## 4. Setup firovm tool ### prerequisites - `Node v18` - `yarn` ```sh unzip firovm-tool-master.zip cd firovm-tool-master yarn build ``` # 1. Setup admin keys (At least 2) These keys would be used to manage network following - Add/Remove miners - Update network params - Setup network You can use CLI to generate following command below **NOTE: this is for testing, on production we should generate it offline** ```sh! # run this on admin node $CLI createwallet '' admin1=$($CLI getnewaddress 'admin') hex_admin1=$($CLI gethexaddress $admin1) admin2=$($CLI getnewaddress 'admin') hex_admin2=$($CLI gethexaddress $admin2) ``` # 2. Funding miner ```shell $CLI generatetoaddress 1 $admin1 ``` # 3. Generate first 2500 blocks We have to generate some fund so we can create transactions to manage network. For the section below we show the example to generate using cli. ```sh! current=$($CLI getblockcount) target=2500 address=$($CLI fromhexaddress 0000000000000000000000000000000000000000) while [ "$target" -gt "$current" ]; do diff=$(expr $target - $current) if [ "$diff" -gt "10" ]; then diff=10 fi $CLI generatetoaddress $diff $address > /dev/null current=$($CLI getblockcount) printf "\rgenerated: %d/%d" $current $target done ``` # 3. Initilize contracts In our network contains 3 main contracts - **QtumGov**: 0000000000000000000000000000000000000083 - To manage admins and govs list - **MinerList**: 0000000000000000000000000000000000000880 - To store miners and their UTXOs - To allow UTXOs list to be updated after used - **Mobile**: 0000000000000000000000000000000000000881 - **NOTE: untested** ## 3.1 Set initial admin Initializing **QtumGov** contract by calling function below. Caller of this function would become the first admin so in the example we use **\$admin1** ```solidity function setInitialAdmin() external { ... } ``` Calling contract **QtumGov** with fixed payload `6fb81cb` ### Using CLI ```sh! firovm-cli sendtocontract 0000000000000000000000000000000000000083 6fb81cbb 0 200000 0.0000004 $admin1 # to make sure contract state be changed firovm-cli generatetoaddress 1 $admin1 ``` ## 3.2 Set QtumGov address to MinerList and Mobile contracts **MinerList** and **Mobile** contracts must be set **QtumGov** address before using by calling function by **\$admin1** ```solidity function setDGPContract(address qtumGov) external { ... } ``` Calling contract **MinerList** and **Mobile** using payload `9f080402000000000000000000000000` + **QtumGov** ### Using CLI ```sh! # MinerList contract firovm-cli sendtocontract 0000000000000000000000000000000000000880 9f0804020000000000000000000000000000000000000000000000000000000000000083 0 1000000 0.0000004 $admin1 # Mobile contract firovm-cli sendtocontract 0000000000000000000000000000000000000881 9f0804020000000000000000000000000000000000000000000000000000000000000083 0 1000000 0.0000004 $admin1 # to make sure contract state be changed firovm-cli generatetoaddress 1 $admin1 ``` ## 3.3 Set Miner and Mobile contracts on QtumGov To sync admins and govs list we have to add **MinerList** and **Mobile** contracts to **QtumGov** contract by calling function by **admin1** ```solidity function setContracts(address __validatorContractAddress, address __poaContractAddress) public onlyAdmin { ... } ``` Calling contract **QtumGov** with payload `d8952a49000000000000000000000000` + **MinerList** + `000000000000000000000000` + **Mobile** ### Using CLI ```sh! firovm-cli sendtocontract 0000000000000000000000000000000000000083 d8952a4900000000000000000000000000000000000000000000000000000000000008800000000000000000000000000000000000000000000000000000000000000881 0 1000000 0.0000004 $admin1 # to make sure contract state be changed firovm-cli generatetoaddress 1 $admin1 ``` ## 3.4 Add one more admin We can add more admin by calling function by **\$admin1** ```solidity function addAddressProposal(address _proposalAddress, uint _type) external { ... } ``` Calling contract **QtumGov** with payload `bf5f1e83000000000000000000000000` + **hex address of new admin** + `0000000000000000000000000000000000000000000000000000000000000000` ### Using CLI ```sh! hex_address2=$(firovm-cli gethexaddress $admin2) calldata=bf5f1e83000000000000000000000000 calldata+=$hex_address2 calldata+=0000000000000000000000000000000000000000000000000000000000000000 firovm-cli sendtocontract 0000000000000000000000000000000000000083 $calldata 0 1000000 0.0000004 $admin1 # to make sure contract state be changed firovm-cli generatetoaddress 1 $admin1 ``` # 4. Prepare Miners ## 4.1 Generate miner addresses on miner nodes ```sh! firovm-cli createwallet '' miners=10 # number of miner addresses for i in $(seq 1 $firovm) do firovm-cli getnewaddress 'miner' >> miners.txt done # then send miners.txt to admins ``` ## 4.2 Use script to fund and register miners Admins can add new miner addresses one by one by using script `addminer.sh` which is provided in the last section **Script** ```sh! addminer.sh <address> <number of utxos> $admin1 $admin2 ``` **Arguments** - **address**: miner addresses generate from 4.1 - **number of utxos**: to generate which should not more than 10 - **admin1**, **admin2**: generated from 1.1 **Environments** - **CLI=<firovm-cli path>**: to tell the script where to find rpc client. The default value would be `./firovm-cli` - you may use docker exec by set `CLI=docker exec -it firovm firovm-cli` - **JQ**: path of jq the JSON processor. The default value would be `/usr/bin/jq` **Example** ```sh! CLI="docker exec -it firovm firovm-cli" JQ="./bin/jq" addminer.sh TEQa8Qug3qsuWUNutcy1bpachTwoCZCzw2 10 $admin1 $admin2 ``` Then add admin from addresses file line by line ```sh! CLI="..." while read addr; do if [ ! -z $addr ]; then CLI=$CLI ./addminer.sh $addr 10 $admin1 $admin2 fi done < miners.txt ``` # 5. Generate more blocks ```sh! CLI=firovm-cli current=$($CLI getblockcount) target=8000 address=<address> while [ "$target" -gt "$current" ]; do diff=$(expr $target - $current) if [ "$diff" -gt "10" ]; then diff=10 fi $CLI generatetoaddress $diff $address > /dev/null current=$($CLI getblockcount) printf "\rgenerated: %d/%d" $current $target done ``` # 6. Check PoA status ### a) PoA block Check the latest block ``` firovm-cli getblock $(firovm-cli getblockhash $(firovm-cli getblockcount)) ``` If the latest block is **proof of authority** the result should contains `"flags": "proof-of-stake"` ### b) Miner status Message like below should be logged ``` SloveBlock(): coins=1000, solved=1 ``` which **coins** represents the number of coins which could be used to generate blocks and **solved** represents number of coins that qualify to generate the next block. # 7. Propose supply mint To mint supply we will work on contract `0000000000000000000000000000000000000882` and we will use list of `admins` and `govs` to vote by using 2 of 3 To encode calldata we recommend to compile and deploy [SupplyControlABIEncoder.sol](#SupplyControlABIEncodersol) contract on [Remix IDE](https://remix.ethereum.org). And then you can encode payload using GUI ## 7.1 Create proposal To mint native token we have to create proposal and allow `admins` and `govs` to vote ```solidity function propose( uint256 _block, // block to mint uint256[] calldata _amounts, // array of amount to mint to scripts string[] calldata _scriptTypes, // array of script type for the most cast should be "pkh" bytes[] calldata _scripts // array of script to payout e.g. // 0x76a91413c03891b1a982bb15620def6c70225a5829b8e288ac ) { ... } ``` **NOTE**: encode using Remix IDE ### Using CLI ```sh! $CLI sendtocontract 0000000000000000000000000000000000000882 <calldata> 0 200000 0.0000004 $admin1 # to make sure contract state be changed $CLI generatetoaddress 1 $admin1 # then you can get proposalId using $CLI gettransactionreceipt <txid from `sendtocontract`> # Then you can getproposal ID from [0].log[0].topics[1] # [ # { # "blockHash": "5ef54892242772d6c4a1fd15fc450811f4bc7d9515f4d6914ee039c05f79bb0d", # "blockNumber": 2019, # "transactionHash": "952bc5265669f8f29b0a8f19d2d74dc56369f16be1e9270dd7faee06b645ad93", # "transactionIndex": 1, # "outputIndex": 0, # "from": "66132fa10e1660ebe7cd9a4af6f31b1622df6ff7", # "to": "0000000000000000000000000000000000000882", # "cumulativeGasUsed": 276559, # "gasUsed": 276559, # "contractAddress": "0000000000000000000000000000000000000882", # "excepted": "None", # "exceptedMessage": "", # "bloom": "00000000000000000000000020000000000000000000100000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000100008000000000000000000000000000100000000000000000000000", # "stateRoot": "e5a0087aeffe04fd465100f2d93416b1f51b811cae260af23a17099b004d525f", # "utxoRoot": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", # "log": [ # { # "address": "0000000000000000000000000000000000000882", # "topics": [ # "c2c021f5d73c63c481d336fbbafec58f694fc45095f00b02d2deb8cca59afe07", # "1d460b64f7b8ba0be629afe9b4ae65333b379985d7ea823ff4c0b8c3b5102153" << proposal ID # ], # "data": "" # } # ] # } # ] ``` ## 7.2 Cast vote on the proposal We can cast vote on proposal by calling function ```solidity function castVote( uint256 proposalId, // from 7.1 uint8 support // 0 = vote agaist, 1 = vote for, 2 = abstain // in this test we will use 1 ) public { ... } ``` **NOTE**: encode using Remix IDE ### Using CLI ```sh! $CLI sendtocontract 0000000000000000000000000000000000000882 <calldata> 0 200000 0.0000004 $admin1 $CLI sendtocontract 0000000000000000000000000000000000000882 <calldata> 0 200000 0.0000004 $admin2 # to make sure contract state be changed $CLI generatetoaddress 1 $admin1 ``` ## 7.3 Execute proposal We can cast vote on proposal by calling function ```solidity function execute( uint256 proposalId // proposal id from 7.1 ) public { ... } ``` **NOTE**: encode using Remix IDE ### Using CLI ```sh! # To make block number over voting period $CLI generatetoaddress 5760 $admin1 $CLI sendtocontract 0000000000000000000000000000000000000882 <calldata> 0 200000 0.0000004 $admin1 # to make sure contract state be changed $CLI generatetoaddress 1 $admin1 ``` ## 7.4 Verify We can cast vote on proposal by calling function ```solidity function getMint( uint256 _block // block number specify on 7.1 ) public { ... } ``` ### Using CLI ```sh! $CLI callcontract 0000000000000000000000000000000000000882 <calldata> ``` ## CODE ### `addminer.sh` ```sh! #!/bin/bash CLI=$CLI if [ -z "$CLI" ]; then CLI="firovm-cli" fi JQ=$JQ if [ -z "$JQ" ]; then JQ="/usr/bin/jq" fi ADDRESS=$1 HEX_ADDRESS=$($CLI gethexaddress $ADDRESS) UTXOS=$2 ADMIN1=$3 ADMIN2=$4 MINER_LIST="0000000000000000000000000000000000000880" for i in $(seq 1 $UTXOS) do $CLI sendtoaddress $ADDRESS 100 >/dev/null MOD=$(($i%10)) if [ "$MOD" -eq "0" ]; then $CLI generatetoaddress 1 $ADMIN1 >/dev/null fi done echo "generated UTXOS" $CLI generatetoaddress 1 $ADMIN1 >/dev/null ## contract payload utxos=$($CLI getaddressutxos "{\"addresses\":[\"${ADDRESS}\"]}") _len=$(printf "%064x" $($JQ '. | length' <<< "$utxos")) calldata="c5bc45af000000000000000000000000" calldata+="${HEX_ADDRESS}" calldata+="0000000000000000000000000000000000000000000000000000000000000060" calldata+="0000000000000000000000000000000000000000000000000000000000000001" calldata+="${_len}" calldata+=$( $JQ -c '.[] | objects' <<< "$utxos" | while read obj; do txid=$($JQ '.txid' <<< $obj | tr -d '"') outputIndex=$(printf "%064x" $($JQ '.outputIndex' <<< $obj)) printf "%s%s" $outputIndex $txid done ) proposal=$($CLI sendtocontract $MINER_LIST $calldata 0 1000000 0.0000004 $ADMIN1 | $JQ -r '.txid') $CLI generatetoaddress 1 $ADMIN1 >/dev/null receipts=$($CLI gettransactionreceipt $proposal) if [ "$($JQ '.|length' <<< $receipts)" -eq "0" ]; then echo "fail to create proposal"; exit 0; fi receipt=$($JQ -r '.[0]' <<< $receipts) if [ "$($JQ -r '.excepted' <<< $receipt)" != "None" ]; then echo "error on create proposal $receipt"; exit 0; fi logs=$($JQ -r '.log' <<< $receipt) if [ $($JQ '.|length' <<< $logs) -eq "0" ]; then echo "no log in receipt"; exit 0; fi proposalid=$($JQ -r '.[0].data' <<< $logs) echo "proposalid: ${proposalid}" # vote calldata="943e8216${proposalid}0000000000000000000000000000000000000000000000000000000000000001" voteid=$($CLI sendtocontract $MINER_LIST $calldata 0 1000000 0.0000004 $ADMIN2 | $JQ -r '.txid') $CLI generatetoaddress 1 $ADMIN1 >/dev/null receipts=$($CLI gettransactionreceipt $voteid) if [ "$($JQ '.|length' <<< $receipts)" -eq "0" ]; then echo "fail to vote"; exit 0; fi receipt=$($JQ -r '.[0]' <<< $receipts) if [ "$($JQ -r '.excepted' <<< $receipt)" != "None" ]; then echo "error on vote"; exit 0; fi echo "vote: $voteid" # add one more for readding $CLI sendtoaddress $ADDRESS 100 >/dev/null $CLI generatetoaddress 1 $ADMIN1 >/dev/null echo "Success to add miner $ADDRESS" ``` ### SupplyControlABIEncoder.sol ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract SupplyControlABIEncoder { function propose( uint256 _block, uint256[] calldata _amounts, string[] calldata _scriptTypes, bytes[] calldata _scripts ) public {} function castVote(uint256 proposalId, uint8 support) public {} function execute(uint256 proposalId) public {} function getMint(uint256 _block) public {} } ```