待完善稿件

281 阅读5分钟

Conflux网络基本介绍

Conflux是一种新型的安全可靠的公共区块链,具有高性能和可扩展性。Conflux可以实现与比特币和以太坊相同的去中心化水平和安全性,同时能够在交易吞吐量(TPS)和最终延迟方面有两个数量级以上的改进。

通过使用js-conflux-sdk这一基于JavaScript语言的集成开发库配合npm包管理,能够开发基于Conflux网络的DApp,开发编译部署感兴趣的智能合约到Conflux网络中。我希望通过本文的记录与整理,为更多对Conflux这一高性能公链感兴趣的同学们尝试使用js-conflux-sdk更加方便的开发自己所感兴趣的项目提供可能。

相较于web3,js-conflux-sdk对其逻辑进行了一系列改进与优化,本文将重点介绍使用该SDK开发调用相关智能合约的方法。如果您熟悉编写智能合约的方法,能够利用智能合约做更多有意思的事情。

环境配置

首先您需要安装nodejs作为运行环境,我是参考文档完成对nodejs环境的部署操作。

  1. 选择您希望存放DAPP的文件夹作为您的工作目录
  2. 在工作目录下运行npm的init命令以创建 package.json 文件,该文件能够帮助您存储项目名称、项目版本、项目描述、入口点文件、项目依赖信息等关键信息:
npm init -y

-y参数可以帮助您省去敲回车的繁琐,当然如果您希望通过交互命令,填写您的项目信息,可以直接使用:

npm init
  1. 通过修改 package.json 使其满足您的项目配置,此处为我创建的DAPP所对应的内容:
{
  "name": "contract-project",
  "version": "1.0.0",
  "description": "smart Contract",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Yumingyuan",
  "license": "ISC"
}

此外需要通过npm包管理工具在项目路径下安装一些必要的依赖包,其命令如下:

npm install solc@0.5.11

通过@操作符控制安装的版本,以确保编译器与我们的代码兼容。

  1. 此外还需要通过npm命令在同一工作目录下安装JavaScript Conflux Software Development Kit
npm install js-conflux-sdk

安装完后,您会发现在相应的目录下出现了一个文件夹 node_modules,他是在安装sdk时自动生成的。

尝试开发

  1. 如果需要编写智能合约,我们还需要尝试使用Solidity这门面向智能合约且为了实现智能合约而创建的高级编程语言开发智能合约。Solidity文件的对应后缀为sol,为了方便进行开发,我们在项目文件夹下创建了一个contracts文件夹。并将我们编写的智能合约代码存入 ./contracts/test.sol 内,其内容如下:
pragma solidity ^0.5.0;

contract Counter {
    uint public count=0;
    event SelfEvent(address indexed sender, uint current);

    constructor() public {
    }

    function inc(uint num) public returns (uint){
        return count += num;
    }

    function self() public {
        emit SelfEvent(msg.sender, count);
    }
}
  1. 尝试Solidity编程的方式可以是使用Remix,可以通过将自己编写的test.sol文件上传至Remix进行编译,但我们看到了另外一个有意思的方法(后续步骤有说明),固此处截图仅为样例:

test.sol at Remix

  1. 有位外国大佬,他编写了一份专门可以用于编译 sol 的JS脚本,其内容如下,我们将它保存为 compile.js 并将我们编写的 test.sol 文件放入 ./contracts/test.sol 下,同时将 compile.js 放在 ./ 下,这段代码的逻辑是读取 contracts 文件夹内合约并保存其abi和bytecode为json文件,存储到项目 ./build 文件夹下:
const path = require('path');
const fs = require('fs-extra');
const solc = require('solc');

const sourceFolderPath = path.resolve(__dirname, 'contracts');
const buildFolderPath = path.resolve(__dirname, 'build');


const getContractSource = contractFileName => {
    const contractPath = path.resolve(__dirname, 'contracts', contractFileName);
    return fs.readFileSync(contractPath, 'utf8');
};

let sources = {};

var walk = function (dir) {
    var results = [];
    var list = fs.readdirSync(dir);
    list.forEach(function (file) {
        file = dir + '/' + file;
        var stat = fs.statSync(file);
        if (stat && stat.isDirectory()) {
            results = results.concat(walk(file));
        } else {
            if (file.substr(file.length - 4, file.length) === ".sol") {
                sources = {
                    ...sources,
                    [file]: {
                        content: getContractSource(file)
                    }
                };
            }
            results.push(file);
        }
    });
    return results;
};
walk(sourceFolderPath);

const input = {
    language: 'Solidity',
    sources,
    settings: {
        outputSelection: {
            '*': {
                '*': ['*']
            }
        }
    }
}

console.log('\nCompiling contracts...');
const output = JSON.parse(solc.compile(JSON.stringify(input)));
console.log('Done');

let shouldBuild = true;

if (output.errors) {
    console.error(output.errors);
    // throw '\nError in compilation please check the contract\n';
    for (error of output.errors) {
        if (error.severity === 'error') {
            shouldBuild = false;
            throw 'Error found';
            break;
        }
    }
}

if (shouldBuild) {
    console.log('\nBuilding please wait...');

    fs.removeSync(buildFolderPath);
    fs.ensureDirSync(buildFolderPath);

    for (let contractFile in output.contracts) {
        for (let key in output.contracts[contractFile]) {
            fs.outputJsonSync(
                path.resolve(buildFolderPath, `${key}.json`),
                {
                    abi: output.contracts[contractFile][key]["abi"],
                    bytecode: output.contracts[contractFile][key]["evm"]["bytecode"]["object"]
                },
                {
                    spaces: 2,
                    EOL: "\n"
                }
            );
        }
    }
    console.log('Build finished successfully!\n');
} else {
    console.log('\nBuild failed\n');
}
  1. 通过在命令行运行 compile.js 编译 ./Contracts/test.sol
node compile.js

编译成功情况下的运行结果如下:

node_compile

  1. 可以看一下 ./build 文件夹内的数据,发现该编译工具只生成了一个文件 Counter.json ,其内容如下:
{
  "abi": [
    {
      "constant": true,
      "inputs": [],
      "name": "count",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [],
      "name": "self",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [
        {
          "internalType": "uint256",
          "name": "num",
          "type": "uint256"
        }
      ],
      "name": "inc",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "sender",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "current",
          "type": "uint256"
        }
      ],
      "name": "SelfEvent",
      "type": "event"
    }
  ],
  "bytecode": "60806040526000805534801561001457600080fd5b50610154806100246000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd146100465780637104ddb214610064578063812600df1461006e575b600080fd5b61004e6100b0565b6040518082815260200191505060405180910390f35b61006c6100b6565b005b61009a6004803603602081101561008457600080fd5b8101908080359060200190929190505050610108565b6040518082815260200191505060405180910390f35b60005481565b3373ffffffffffffffffffffffffffffffffffffffff167fc4c01f6de493c58245fb681341f3a76bba9551ce81b11cbbb5d6d297844594df6000546040518082815260200191505060405180910390a2565b60008160008082825401925050819055905091905056fea265627a7a72315820c8c117cfc48f53525a11915e1966d2ea2561b1da9f868f08d5fdb00422aff8d264736f6c634300050b0032"
}

这还没完,您需要在其中找到 bytecode 并将这一键对应的值前增加 0x ,增加后的效果如下:

{
  "abi": [
    {
      "constant": true,
      "inputs": [],
      "name": "count",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [],
      "name": "self",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [
        {
          "internalType": "uint256",
          "name": "num",
          "type": "uint256"
        }
      ],
      "name": "inc",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "sender",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "current",
          "type": "uint256"
        }
      ],
      "name": "SelfEvent",
      "type": "event"
    }
  ],
  "bytecode": "0x60806040526000805534801561001457600080fd5b50610154806100246000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd146100465780637104ddb214610064578063812600df1461006e575b600080fd5b61004e6100b0565b6040518082815260200191505060405180910390f35b61006c6100b6565b005b61009a6004803603602081101561008457600080fd5b8101908080359060200190929190505050610108565b6040518082815260200191505060405180910390f35b60005481565b3373ffffffffffffffffffffffffffffffffffffffff167fc4c01f6de493c58245fb681341f3a76bba9551ce81b11cbbb5d6d297844594df6000546040518082815260200191505060405180910390a2565b60008160008082825401925050819055905091905056fea265627a7a72315820c8c117cfc48f53525a11915e1966d2ea2561b1da9f868f08d5fdb00422aff8d264736f6c634300050b0032"
}

为了方便大家比较差异,我也只能把内容全部贴到这里,还请大家包容理解,请您一定要找到这个差异并在编译后添加 0x 否则,在后续部署合约时,会出现错误。abi和bytecode都写在同一个json文件内的好处是,我们只需要读取 Counter.json 文件就可以使用他们调用Conflux创建智能合约

  1. 智能合约编译完了,我们需要把它部署到Conflux网络中,还需要您花一些时间做如下的准备工作:
  • 参考如何创建Conflux网页钱包创建属于您自己的Conflux钱包;
  • 登录钱包后,点击页面中 申领测试币 按钮,申请100CFX测试币,后续部署合约需要进行消费;
  • 点击左侧栏目中的 钱包私钥 获取您的私钥,这个私钥仅仅用于部署合约,不要告诉别人,因为可以直接通过私钥进入到您的钱包,不能进行公开。
  1. 编写 deploy.js ,其目的是将所写合约部署到Conflux网络,且部署的位置就是您填写私钥对应的合约地址(后续会有讲解哦,不要着急!)
// 填写您的私钥地址,为了减少信息泄露,我使用的测试账户私钥后20位已使用星号进行替代处理,不要复制我的哈!
const PRIVATE_KEY = '0x2772b19636f1d183a9a2a0d27da2a1d0efb97637b425********************';
// 合约地址
const CONTRACT = '';
const { Conflux } = require('js-conflux-sdk');
const compiled = require(`./build/${process.argv[2]}.json`);
async function main() {
  const cfx = new Conflux({
    url: 'http://mainnet-jsonrpc.conflux-chain.org:12537',//Conflux主网地址
    defaultGasPrice: 101,
    defaultGas: 1000000,
  });
  const account = cfx.Account(PRIVATE_KEY); // create account instance
  console.log(account.address); 

  // create contract instance
  const contract = cfx.Contract({
	  
    abi: compiled.abi,
    bytecode: compiled.bytecode,
	nonce : 37, 
	
  });

  const receipt = await contract.constructor()
    .sendTransaction({ from: account })
    .confirmed();
  console.log("recv:"+receipt.contractCreated); 
}
main().catch(e => console.error(e));

需要说明的是:nonce是账户的交易序号每次一发起交易(包括部署合约)会使用一个nonce成功后nonce自增1,而如果交易执行不成功时nonce不会自增1(我在这里指定了nonce,您也可以把它删除以使用Conflux的默认值),再次部署的时候,因为系统中已存在一个错误而不能执行的nonce,会报nonce已存在错误运行效果如下图所示:

nonce_error

部署与调用

  1. 运行 deploy.js 的方法如下:
node deploy.js <contract_name>

以我们撰写的合约为例,方法如下:

node deploy.js test

需要说明的是 test 是命令行参数,通过 deploy.js 内的代码 process.argv[2] 获得,帮助我们程序去 ./contracts/ 文件夹内寻找名为 test.json 并进行部署,其输出结果如下图所示:

  1. 通过程序回显 recv:0x8811f30205ab98b1ec6afce4d8e6583db084e479 获取到了我们部署合约的地址为 0x8811f30205ab98b1ec6afce4d8e6583db084e479 ,使用该地址继续编写 call.js 代码,访问并调用合约:
const { Conflux, util } = require('js-conflux-sdk');
// 合约地址:上面已经进行解释
const contractAddress = '0x8811f30205ab98b1ec6afce4d8e6583db084e479';
const PRIVATE_KEY = '0x2772b19636f1d183a9a2a0d27da2a1d0efb97637b425********************';
const compiled = require(`./build/Counter.json`)
async function main() {
  const cfx = new Conflux({
    url: 'http://mainnet-jsonrpc.conflux-chain.org:12537',
    defaultGasPrice: 100,
    defaultGas: 1000000,
  });
  const contract = cfx.Contract({
    address : contractAddress,
    abi: compiled.abi,
  });
  // 不进行记录
  let inc = await contract.inc(10);
  console.log("1 output:"  + inc.toString());
  const account = cfx.Account(PRIVATE_KEY);
  
  // 进行记录并花费燃油
  await contract.inc(10).sendTransaction({ from: account }).confirmed();

}
main().catch(e => console.error(e));
  1. 调用结果如下,由于是第一次调用,在此逻辑下,输出结果为 10
    call_result