# 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 {}
}
```