跳到主要内容

入门手册

入门手册#

概要#

本教程主要是指导用户在PlatON上使用wasm语言创建简单的HelloWorld智能合约,通过platon-truffle编译,部署,调用此合约。

platon-truffle开发工具介绍#

platon-truffle是PlatON提供的一款能够在本地编译、部署、调用智能合约的工具,具体的安装及使用手册参见

创建HelloWorld合约#

#include <platon/platon.hpp>#include <string>using namespace platon;
class message {   public:      std::string head;      PLATON_SERIALIZE(message, (head))};
class my_message : public message {   public:      std::string body;      std::string end;      PLATON_SERIALIZE_DERIVED(my_message, message, (body)(end))};
CONTRACT HelloWorld : public platon::Contract{   public:      ACTION void init(const my_message &one_message){        info.self().push_back(one_message);      }
      ACTION void add_message(const my_message &one_message){          info.self().push_back(one_message);      }
      CONST uint8_t get_message_size(){          return info.self().size();      }
      CONST std::string get_message_body(const uint8_t index){          return info.self()[index].body;      }
   private:      platon::StorageType<"myvector"_n, std::vector<my_message>> info;};
PLATON_DISPATCH(HelloWorld, (init)(add_message)(get_message_size)(get_message_body))

合约文件说明

  • 每一个合约文件只有一个合约类,合约类用 CONTRACT 修饰, 必须公有继承 platon::Contract,必须要有 init 函数。

  • ACTION 和 CONST 修饰的成员函数表示可调用函数,此类成员函数不可以重载。ACTION 函数会修改链上数据,CONST 函数只是查询属性不会修改链上数据。

  • 可调用函数参数列表中的类型为自定义类型,此类型定义中需加上 PLATON_SERIALIZE 宏声明序列化函数,此类型继承自其他类型,需加上 PLATON_SERIALIZE_DERIVED 宏声明序列化函数。

  • 可调用函数只有在PLATON_DISPATCH 宏定义统一入口函数,才能够被外部调用。

  • 目前 platon 会将合约类的成员变量持久化存储,成员变量必须是 platon::StorageType 类型,platon::StorageType模板的第一个参数字符串后面加上_n,字符串必须为.12345abcdefghijklmnopqrstuvwxyz这32字符中的字符。第二个参数为实际存储的具体类型。成员函数修改成员变量需要通过 self() 函数获取具体类型的实例,然后执行相应的实例函数。

  • platon::StorageType 模板的第二个参数类型为自定义类型,此类型定义中需加上 PLATON_SERIALIZE 宏声明序列化函数,此类型继承自其他类型,需加上 PLATON_SERIALIZE_DERIVED 宏声明序列化函数。

编译HelloWorld合约#

step1. 为HelloWorld项目创建新目录

mkdir HelloWorld && cd HelloWorld
  • 以下命令如果没有特殊说明都在HelloWorld目录下进行

step2. 使用platon-truffle初始化一个工程

platon-truffle init

在操作完成之后,就有如下项目结构:

  • contracts/: wasm合约目录

  • migrations/: 部署脚本文件目录

  • test/: 测试脚本目录

  • truffle-config.js: platon-truffle 配置文件

step3. 将之前编写好的HelloWorld合约放至HelloWorld/contracts/目录下

ls contracts/
  • 将看到HelloWorld.cpp

step4. 修改platon-truffle 配置文件truffle-config.js,添加编译wasm合约版本号

vim truffle-config.js

truffle-config.js 修改部分内容如下:

compilers: {     wasm: {           version: "1.0.0"     }}

step5. 编译合约

platon-truffle compile

在操作完成之后,生成如下目录结构:

  • build/ wasm合约编译后的目录
  • build/contracts/HelloWorld.abi.json HelloWorld合约编译后的abi接口文件
  • build/contracts/HelloWorld.wasm HelloWorld合约编译后的二进制文件

部署HelloWorld合约#

step1. 修改truffle-config.js中链的配置信息

vim truffle-config.js

将truffle-config.js中的区块链相关配置修改成您真实连接的链配置

networks: {    development: {       host: "10.1.1.6",     // 区块链所在服务器主机       port: 8806,            // 链端口号       network_id: "*",       // Any network (default: none)       from: "lat1wxadw8yzr6qxdw5yl3f2surp6ue6f03e07kcqc",       gas: 999999,       gasPrice: 50000000004,    },}

step2. 解锁钱包账户

进入platon-truffle控制台

platon-truffle console

导入私钥(如果之前已导入可以跳过此步骤)

web3.platon.personal.importRawKey("您的钱包私钥","您的钱包密码");

导入成功将看到私钥对应的地址:

'lat1wxadw8yzr6qxdw5yl3f2surp6ue6f03e07kcqc'

解锁钱包账户

 web3.platon.personal.unlockAccount('您的钱包地址','您的钱包密码',999999);

解锁成功将看到如下信息:

ture

step3. 部署HelloWorld合约

platon-truffle deploy --wasm --contract-name HelloWorld --params '[[["1"], "2", "3"]]'
  • HelloWorld :要部署的合约
  • params :合约init函数对应的参数

部署成功后,将看到类似如下信息:

receipt:  { blockHash:   '0x266733b693ba650315a59c34e72804c06ca3e27fab145625797bd42259b572c5',  blockNumber: 70441,  contractAddress: 'lat1nk4errnjlulaz5y57xt4djkx3h4jjl7yy9dzqn',  cumulativeGasUsed: 291314,  from: 'lat1wxadw8yzr6qxdw5yl3f2surp6ue6f03e07kcqc',  gasUsed: 291314,  logs: [],  logsBloom:   '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',  status: true,  to: null,  transactionHash:   '0x60946ebf0ccddc76a0234353435de73e7901888227fb2f03922fb0ce265a4e9d',  transactionIndex: 0 }  contract HelloWorld deployed successfully======================
   > transactionHash:     0x60946ebf0ccddc76a0234353435de73e7901888227fb2f03922fb0ce265a4e9d   > contract address:    lat1nk4errnjlulaz5y57xt4djkx3h4jjl7yy9dzqn   > block number:        70441   > block timestamp:     1583247148341   > account:             lat1wxadw8yzr6qxdw5yl3f2surp6ue6f03e07kcqc   > balance:             3533694129556768659166595001485837031654967793751237866225582808584074296   > gas limit:           100000000   > gas used:            291314   > gas price:           0.000000050000000004 LAT   > total cost:          0.014565700001165256 LAT

调用HelloWorld合约#

step1. 进入platon-truffle控制台

platon-truffle console
  • 以下调用查询将在platon-truffle控制台中进行

step2. 构建合约对象

var abi = [{"baseclass":[],"fields":[{"name":"head","type":"string"}],"name":"message","type":"struct"},{"baseclass":["message"],"fields":[{"name":"body","type":"string"},{"name":"end","type":"string"}],"name":"my_message","type":"struct"},{"constant":false,"input":[{"name":"one_message","type":"my_message"}],"name":"init","output":"void","type":"Action"},{"constant":false,"input":[{"name":"one_message","type":"my_message"}],"name":"add_message","output":"void","type":"Action"},{"constant":true,"input":[],"name":"get_message_size","output":"uint8","type":"Action"},{"constant":true,"input":[{"name":"index","type":"uint8"}],"name":"get_message_body","output":"string","type":"Action"}];var contractAddr = 'lat1nk4errnjlulaz5y57xt4djkx3h4jjl7yy9dzqn'; var helloworld = new web3.platon.Contract(abi,contractAddr,{vmType: 1 }); 

说明:

  • abi 是合约提供给外部调用时的接口,每个合约对应的abi在编译后的文件中,如:HelloWorld/build/contracts/HelloWorld.json 中可以找到
  • contractAddr 在部署合约成功后有一个contract address
  • helloWorld 就是构建出来与链上合约交互的合约对象抽象

step3. 调用合约

helloworld.methods.add_message([["5"], "6", "7"]).send({    from: 'lat1wxadw8yzr6qxdw5yl3f2surp6ue6f03e07kcqc',gas: 999999}).on('receipt', function(receipt) {    console.log(receipt);}).on('error', console.error);

调用合约命令说明:

  • helloWorld 是之前构建的合约对象
  • methods 固定语法,后面紧跟合约的方法名
  • add_message 是我们HelloWorld合约中的一个方法,有一个自定义my_message类型的入参
  • from 调用者的钱包地址
  • gas gas值
  • on 是监听合约处理结果事件,此处如果成功将打印回执,失败输出错误日志

函数调用成功,将会看到如下信息:

{ blockHash:   '0x669c7b8cb938cc30845e08dc4ceda08f2e17207c267ade97dc5458b445405736',  blockNumber: 74665,  contractAddress: null,  cumulativeGasUsed: 108549,  from: 'lat1wxadw8yzr6qxdw5yl3f2surp6ue6f03e07kcqc',  gasUsed: 108549,  logsBloom:   '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',  status: true,  to: 'lat1nk4errnjlulaz5y57xt4djkx3h4jjl7yy9dzqn',  transactionHash:   '0x2b5e590df7e70ad428b1ccb06bda5dcce47f84c4d981c2fb475aca9ec9d0000a',  transactionIndex: 0,  events: {} }{ blockHash:   '0x669c7b8cb938cc30845e08dc4ceda08f2e17207c267ade97dc5458b445405736',  blockNumber: 74665,  contractAddress: null,  cumulativeGasUsed: 108549,  from: 'lat1wxadw8yzr6qxdw5yl3f2surp6ue6f03e07kcqc',  gasUsed: 108549,  logsBloom:   '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',  status: true,  to: 'lat1nk4errnjlulaz5y57xt4djkx3h4jjl7yy9dzqn',  transactionHash:   '0x2b5e590df7e70ad428b1ccb06bda5dcce47f84c4d981c2fb475aca9ec9d0000a',  transactionIndex: 0,  events: {} }

step4. 合约查询

helloworld.methods.get_message_body(0).call()

查询合约命令说明:

  • helloWorld 是之前构建的合约对象
  • methods 指定将获取合约中的方法
  • get_message_body 是我们HelloWorld合约中的一个方法,该方法有一个int类型的入参
  • call 指明是查询方法

FAQ#

编译相关#

  1. 使用 platon-cpp 如何编译多个 cpp 文件?

    platon-cpp ./test.cpp ./test1.cpp

    只允许存在一个合约类。

  2. 使用 platon-cpp 编译合约, 如何指定wasm输出目录及文件名?

    使用 -o 参数, 且指定目录时必须同时指定文件名:

    platon-cpp ./test.cpp -o ./out/test.wasm
  3. platon-truffle执行truffle deploy部署合约失败?

确认truffle-config.js中连接的链的配置信息及用户的钱包地址是否正确,钱包是否解锁。

  1. truffle 部署带参数的构造函数合约失败?

如果合约中的init函数带有参数,部署合约时需要指定params参数。

  1. ABI 支持哪些数据类型?

    生成ABI支持的类型和转换规则如下:

类型ABI
boolbool
uint8_tuint8
uint16_tuint16
uint32_tuint32
uint64_tuint64
int8_tint8
int16_tint16
int32_tint32
int64_tint64
bytesuint8[]
std::stringstring
std::vector<T>T[]
std::array[T, N]T[N]
std::pair<T, U>pair<T, U
std::set<T>set<T>
std::map<T, V>map<T, V>
std::list<T>list<T>
FixedHash<N>FixedHash<N>
u128uint128
bigintuint128

合约相关#

  1. 如何实现在 platon 进程输出合约 debug 日志?
  • 合约代码首行加上 #undef NDEBUG, 必须在头文件 include 之前

    #undef NDEBUG
    #include <platon/platon.hpp>
    //...
  • platon 启动命令指定日志级别4, 打开 debug 标志

    ./platon --debug --verbosity 4 ...
  1. 如何编写合约能有效减少 Gas 消耗?

    • 使用引用参数
    void test(const std::string& str) {    // ...}
    • 返回右值引用
    std::vector<std::string>&& test() {    std::vector<std::string> v;    // ...    return std::move(v);}
    • 尽量不要申请大块内存
  2. 使用 StorageType 有哪些地方要注意的?

    • 应该在 init() 初始化
    CONTRAT Hello : public Contract {public:    ACTION void init() {        s_.self() = "test";    }private:    StorageType<"test"_n, std::string> s_;};
    • 建议直接使用 StorageType 的特化类型

      • Uint8
      • Int8
      • Uint16
      • Int16
      • Uint
      • Int
      • Uint64
      • Int64
      • String
      • Vector
      • Set
      • Map
      • Array
      • Tuple
  3. StorageTypeplaton::db::Map, platon::db::Array 有什么区别?

    从底层存储层面来说, StorageType 的实现是作为一个整体序列化, 然后存储到数据 库. 而 platon::db::Mapplaton::db::Array 则将容器的每一个元素序列化后, 作为 K/V 存储到数据库. 对于大规模数据, platon::db::Mapplaton::db::Array 性能 更好.

    在实现合约时, 应根据数据规模, 选择合适的存储结构.

  4. RLP 序列化/反序列化 支持哪些 C++ 标准库类型?

    支持以下 C++ 标准库类型:

    • std::string
    • std::vector
    • std::map
    • std::list
    • std::array
    • std::set
    • std::pair
    • std::tuple
  5. 自定义类型如何支持 RLP 序列化/反序列化?

    • 普通类型使用宏 PLATON_SERIALIZE
    struct Base {    int a;    std::string b;
        PLATON_SERIALIZE(Base, (a)(b));};
    • 派生类使用宏 PLATON_SERIALIZE_DERIVED, 同时基类也要使用宏 PLATON_SERIALIZE
    struct Derived : public Base {    int c;    int d;
        PLATON_SERIALIZE_DERIVED(Derived, Base, (c)(d));};