Tutorials

Conflux入门心得汇总

概念

参考资料:

树图结构

树图结构

由开发者文档所示, 与传统的以太坊的链式结构不同, 该结构是类似于IoTA的有向无环图(DAG)结构的, 具体结构可自行查阅.

基本概念

CFX

CFX为conflux中的token, 对应于以太坊中的Ether. conflux中有多种代币单位, 包括Drip,GDrip, uCFX, CFX. 这几种代币之间的进率如下图所示:

代币进率

CFX用处

  1. 参与链治理 -> 拥有CFX的用户可以参与链治理, 发起投票
  2. 发送交易: 包括转账, 部署与调用合约等
  3. 抵押(staking mechanism):

CFX获取

  1. 挖矿: 当挖到相应的区块后可以获得分红. 分红包括:

    1. 固定奖励2CFX
    2. 执行交易获得的费用
    3. 所有抵押代币所获得的年化率4%的回报(需要加入PoS)
  2. 抵押: 将自己的代币抵押给内置合约, 从而使得该账户参与链治理的权力变大. 内置合约根据规则支付年化4%的回报

  3. 转账: 通过从其他账户转账从而获得代币

ChainID 与 NetworkID

顾名思义, 对链与网络的一个标识

注意点

地址

conflux地址为base32形式, 满足cip317(一种编码方式, 提高安全性且增加了可读性), 与以太坊相对

例子如下:

16 进制地址:0x1386b4185a223ef49592233b69291bbe5a80c527

base32地址:cfx:aak2rra2njvd77ezwjvx04kkds9fzagfe6ku8scz91

在以太坊的common.Address是以16进制的方式存储, 可调用base32地址的GetHexString获得, 也就是说, conflux地址与以太坊地址间存在一对一映射关系

账户

存储与以太坊相同, 以 地址-> 状态的键值对以kvstore存储在MPT树当中 与以太坊账户相同的部分: nonce, balance, codeHash(合约账户)

CONFLUX特有的部分: stakingBalance, storageCollateral, accumulatedInterestReturn, admin, sponsorInfo 具体解释可以查看(https://developer.confluxnetwork.org/introduction/en/conflux_basics)

交易

大致的field与以太坊相同, 主要有from, to, nonce, gasPrice, gas, value, chainId, data, v, r, s 这些field的意义也与以太坊相同

conflux特殊的是有epochHeight字段与storageLimit, 跟区块高度相对, conflux的共识是以epoch去表征区块深度的

交易的生命周期

  1. 准备发送者的私钥和地址
  2. 准备交易的元数据,组装成交易, 并将其encode(keccak256), 私钥进行签名(ECDSA), 再用rlp进行encode -> hexString -> rawtransaction
  3. 通过rpc方法, 将rawtransaction发送至全节点的交易池
  4. 交易被矿工打包进区块
  5. 等待5个epoch -> 状态变为executed, 执行交易 (本地状态进行更新?)
  6. 等待50个epoch -> 状态变为confirmed, 交易被承认 (若未被承认, 则状态回滚) 承认机制, 比较epoch大小
  7. 等待被区块链引用至链上 -> 状态变为Finalized, 完结 (全局状态进行更新?)

与ETH的区别

https://developer.confluxnetwork.org/sending-tx/en/transaction_explain#:~:text=no%20details%20provided.-,Differences%20between%20Conflux%20and%20Ethereum,-%23

存储

需要锁一定数量的资金作为质押, 来作为执行交易时所使用storage需要付出的token, 账户这部分的token会被锁; 计算公式为

X B/64 *(1/16)
# 64字节为一个storage entry

当存储空间的所有权转移时, 新拥有者的token会被锁, 旧拥有者的token会被解除锁定, 并被加回旧拥有者的账户(不发生转账交易, 无法查询)

内置合约

主要有三个合约: AdminControl contract, SponsorWhitelistControl contract, Staking Contract

AdminControl contract

创建合约账户的账户成为该合约的admin, 拥有包括destroy, 白名单等多项权力, 该admin权限可以转移

例子:

  1. A在创建了合约B, 并在合约构建阶段将权力给了C -> C是admin
  2. A调用合约B, 合约B创建了合约C, 且合约C在合约构建阶段将D设为admin. -> 设为admin操作失败, A是C的管理员, B只是消息传递者
  3. 若在例子2中, D地址为零, 则可以成功, 则C没有admin

SponsorWhitelistControl contract

使得余额为零的账户能够调用合约, 该费用由sponsor来支付. 条件:

  1. 调用小于sponsor的上限(该上限可以由sponsor调整)
  2. 调用账户在白名单当中(白名单只有admin与合约本身可以改, sponsor不能改)
  3. sponsor关系可以转移

Staking Contract

账户可以将token质押, 从而获取相应的storage和链治理当中的投票权

注意: 质押操作会被更强大的承诺所覆盖, 如, 明年质押1CFX, 与明年质押2CFX相比. (更强大的比较只是token数量?)

实践

建议在Linux与OSX环境下执行·, Windows需要vs编译器过于不友好

安装环境

根据开发者文档步骤, 安装环境 https://developer.confluxnetwork.org/conflux-doc/docs/installation

启动私有链

修改配置文件

在项目目录下进入run目录

cd run

复制主网toml文件为开发toml文件

cp hydra.toml develpoment.toml

修改development.toml

mode = "dev"
genesis_secrets = "key.txt" ##这个后面需要用
dev_block_interval_ms = 250
jsonrpc_http_port=12537 ##用jsonrpc_local_http_port=12539也可以, 钱包的localhost是12537

从钱包网站上按照步骤获得keystore, 或是从github项目中获取keystore

https://github.com/conflux-fans/conflux-abigen-example/tree/main/keystore
https://wallet.confluxscan.io/login

拉取go-sdk代码

go get github.com/Conflux-Chain/go-conflux-sdk

调用sdk在keystore中创建账户, 并导出账户私钥

func createAcc(client *conflux.Client){
	client, err := conflux.NewClient(local_url, conflux.ClientOption{
    KeystorePath: ".keystore"}) //local_url = "http://127.0.0.1:12537"
    if err != nil {
        panic(err)
    }
    defer client.Close()
	acc1,err := client.AccountManager.Create("test")
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(acc1.String())

	acc2,err := client.AccountManager.Create("test")
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(acc2.String())
}

func exportPriKeys(client *conflux.Client){
   list := client.GetAccountManager().List()
   priv1, err := client.GetAccountManager().Export(list[0], "test")
   if err != nil {
   panic(err)
   }
   priv2, err := client.GetAccountManager().Export(list[1], "test")
   if err != nil {
   panic(err)
   }
   fmt.Println(priv2)
   fmt.Println(priv1)
}

在run目录下将输出的私钥写入key.txt, 一行一串私钥, 由此对应的账户会被分配1000cfx

vim key.txt

清除链上数据, 并重启

sh clear_state.sh

sh test.sh
test.sh :
export RUST_BACKTRACE=1
export RUST_BACKTRACE=full
../target/release/conflux --config development.toml

发送交易

发送交易的sdk如下

func sendTx(client *conflux.Client){
	list := client.GetAccountManager().List()

	var utx types.UnsignedTransaction
	utx.From = &list[0] //use default account if not set
	utx.To = &list[1]

	// unlock account
	err = client.AccountManager.Unlock(acc1, "test")
	if err != nil {
		log.Fatal(err)
	}
	txhash, err := client.SendTransaction(utx)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(txhash)
}

部署与调用合约

部署合约与调用合约基于cfxabigen, 参考https://docs.confluxnetwork.org/go-conflux-sdk/cfxabigen

下载代码

git clone https://github.com/Conflux-Chain/go-conflux-sdk.git

安装工具

go install ./cmd/cfxabigen

ERC20

根据示例生成go文件

cfxabigen --sol token.sol --pkg main --out token.go >> token.go

接着部署与调用合约可完全参考https://github.com/conflux-fans/conflux-abigen-example/blob/main/token/main.go

ERC721

在理解了上述ERC20步骤后, 可参考 https://zhuanlan.zhihu.com/p/400583419 和https://zhuanlan.zhihu.com/p/393935101 进行ERC721合约的部署与调用

首先, 编译合约需要依赖Openzeppelin

需要安装npm

sudo apt install npm

安装Openzeppelin库

npm install @openzeppelin/contracts

在remix中进行编译从而得到字节码与abi, 将其分别写入nft.bin与nft.abi中(注:其中ipfs中的url可以为自己生成的, 也可随意)

用cfxabigen工具进行编译

cfxabigen --abi nft.abi --bin nft.bin --pkg main --out nft.go >> nft.go

根据nft.go进行如下sdk编写

部署合约

func deployNFT(client *conflux.Client){
	err := client.AccountManager.UnlockDefault("test1")
	if err != nil {
		log.Fatal(err)
	}

	//tx, hash, t, err := DeployMain(nil, client)

	tx, hash, _, err := DeployMain(nil, client)
	if err != nil {
		panic(err)
	}
	fmt.Println(tx)
	fmt.Println(hash)

	receipt, err := client.WaitForTransationReceipt(*hash, time.Second)
	if err != nil {
		panic(err)
	}

	logrus.WithFields(logrus.Fields{
		"tx":               tx,
		"hash":             hash,
		"contract address": receipt.ContractCreated,
	}).Info("deploy token done")
}

调用铸造NFT

func mintNFT(client *conflux.Client) {
	acc, _ := client.GetAccountManager().GetDefault()
	err := client.AccountManager.UnlockDefault("test")
	if err != nil {
		panic(err)
	}

	contractAddr := cfxaddress.MustNew("cfx:acdujjy8mrjm3913cnztf0zbgpx5h3fbby7jcwp2pc")

	instance, err := NewMain(contractAddr, client)
	if err != nil {
		panic(err)
	}

	err = client.AccountManager.UnlockDefault("test")
	if err != nil {
		panic(err)
	}

	//to := cfxaddress.MustNew("cfx:aap05tb7b9bsxdn5rn365y3peta8pr6mxefp7m682a")

	comacc, _, _ := acc.ToCommon()
	tx, hash, err := instance.Mint(nil, comacc)
	if err != nil {
		panic(err)
	}

	logrus.WithField("tx", tx).WithField("hash", hash).Info("transfer")
	receipt, err := client.WaitForTransationReceipt(*hash, time.Second)
	if err != nil {
		panic(err)
	}

	logrus.WithField("transfer receipt", receipt).Info()
}

查询账户拥有的nft

func queryNFT(client *conflux.Client){
	acc, _ := client.GetAccountManager().GetDefault()
	err := client.AccountManager.UnlockDefault("test1")
	if err != nil {
		panic(err)
	}

	contractAddr := cfxaddress.MustNew("cfx:acdujjy8mrjm3913cnztf0zbgpx5h3fbby7jcwp2pc")

	instance, err := NewMain(contractAddr, client)
	if err != nil {
		panic(err)
	}

	comacc, _, _ := acc.ToCommon()
	res, err := instance.MainCaller.BalanceOf(nil, comacc)
	if err != nil {
		panic(err)
	}
	res1, _ := instance.MainCaller.Symbol(nil)
	fmt.Println(res)
	fmt.Println(res1)
}

调用后可以获得账户对应的nft和nft对应的symbol

转移nft

func transferNFT(client *conflux.Client){
	acc, _ := client.GetAccountManager().GetDefault()
	err := client.AccountManager.UnlockDefault("test1")
	if err != nil {
		panic(err)
	}

	contractAddr := cfxaddress.MustNew("cfx:acdujjy8mrjm3913cnztf0zbgpx5h3fbby7jcwp2pc")

	instance, err := NewMain(contractAddr, client)
	if err != nil {
		panic(err)
	}

	comacc, _, _ := acc.ToCommon()
	list := client.GetAccountManager().List()
	toacc, _, _ := list[len(list)-1].ToCommon()

	err = client.AccountManager.Unlock(list[len(list)-1], "test")
	if err != nil {
		panic(err)
	}

	id := big.NewInt(0)

	tx, hash, err := instance.TransferFrom(nil, comacc, toacc, id)
	if err != nil {
		panic(err)
	}

	receipt, err := client.WaitForTransationReceipt(*hash, time.Second)
	if err != nil {
		panic(err)
	}

	logrus.WithFields(logrus.Fields{
		"tx":               tx,
		"hash":             hash,
		"contract address": receipt.ContractCreated,
	}).Info("deploy token done")


	res, err := instance.MainCaller.BalanceOf(nil, comacc)
	if err != nil {
		panic(err)
	}

	res1, err := instance.MainCaller.BalanceOf(nil, toacc)
	if err != nil {
		panic(err)
	}

	fmt.Println(res)
	fmt.Println(res1)
}

查询后可以发现一个为0, 一个为1, 则转移成功

ConfluxScan 验证合约

ConfluxScan 提供了合约验证的功能,本文将介绍如何在 Scan 验证一个合约。

为什么要进行合约验证?

合约验证之后会有如下几个好处:

  1. 合约验证之后可在 Scan 上看到 Solidity 源码及 ABI,更加透明,人人可审查合约逻辑。
  2. 验证之后的合约可直接在 Scan 合约详情页,调用合约方法读取合约状态,或通过链接 Fluent 更新合约状态。
  3. 合约验证之后,Scan 会显示绿色✅标识,用户更放心。

Screen Shot 2022-06-22 at 10.56.30.png

Screen Shot 2022-06-22 at 10.56.47.png

如何在 Scan 进行合约验证?

合约详情页,如果合约未验证的话, Contract Tab 会展示合约验证的跳转链接,点击之后可以进入到验证页面

Screen Shot 2022-06-22 at 10.57.27.png

合约验证需要开发者提供合约一些原始信息,Scan 验证服务会用来进行验证操作:

Flatten Solidity 代码

因为目前 ConfluxScan 验证只支持单文件,所以在验证前需要进行 flatten 操作,从而获取主合约及所有依赖的完整代码。目前主流的 Solidity 开发工具均提供 flatten 功能,比如:

npx hardhat flatten

假如开发者的合约项目使用 hardhat,则可以使用如下方式获取 merge 后的合约.

$ npx hardhat flatten ./contracts/ERC20Token.sol > ERC20TokenMerged.sol

该命令执行完之后,项目根目录下会有一个完整的合约代码的 ERC20TokenMerged.sol 文件。可直使用 ConfluxScan 的验证页面的合约文件上传功能上传代码,或手动复制文件内容到源代码编辑框中。

关于 npx hardhat flatten 使用也可参看 How to Flatten a Solidity file using Hardhat.

chainIDE flatten 插件

ChainIDE 是一个在线 Solidity 开发工具,通过插件,可以非常方便的扩展功能, @chainide/solidity-flattener 模块即是用于合并合约代码的插件,点击激活,即会在右侧增加一个 Flattener 功能模块。