- 概述
- 初步了解
- 进阶了解
- PlatON 节点
- 开发者
- 数据分析
- 示例教程
- 以太坊 DApp 快速迁移教程
- PRC721NFT 发行教程
- Wasm教程
- WalletConnect
- 获取随机数教程
- EIP55 和 Bech32 地址格式兼容方案
- 钱包
- 社区项目
入门手册
入门手册
概要
本教程主要是指导用户在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 addresshelloWorld
就是构建出来与链上合约交互的合约对象抽象
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
编译相关
使用
platon-cpp
如何编译多个cpp
文件?platon-cpp ./test.cpp ./test1.cpp
只允许存在一个合约类。
使用
platon-cpp
编译合约, 如何指定wasm输出目录及文件名?使用
-o
参数, 且指定目录时必须同时指定文件名:platon-cpp ./test.cpp -o ./out/test.wasm
platon-truffle执行truffle deploy部署合约失败?
确认truffle-config.js中连接的链的配置信息及用户的钱包地址是否正确,钱包是否解锁。
- truffle 部署带参数的构造函数合约失败?
如果合约中的init函数带有参数,部署合约时需要指定params参数。
ABI 支持哪些数据类型?
生成ABI支持的类型和转换规则如下:
类型 | ABI |
---|---|
bool | bool |
uint8_t | uint8 |
uint16_t | uint16 |
uint32_t | uint32 |
uint64_t | uint64 |
int8_t | int8 |
int16_t | int16 |
int32_t | int32 |
int64_t | int64 |
bytes | uint8[] |
std::string | string |
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> |
u128 | uint128 |
bigint | uint128 |
合约相关
- 如何实现在
platon
进程输出合约 debug 日志?
合约代码首行加上
#undef NDEBUG
, 必须在头文件 include 之前#undef NDEBUG #include <platon/platon.hpp> //...
platon
启动命令指定日志级别4, 打开debug
标志./platon --debug --verbosity 4 ...
如何编写合约能有效减少 Gas 消耗?
- 使用引用参数
void test(const std::string& str) { // ... }
- 返回右值引用
std::vector<std::string>&& test() { std::vector<std::string> v; // ... return std::move(v); }
- 尽量不要申请大块内存
使用
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
- 应该在
StorageType
与platon::db::Map
,platon::db::Array
有什么区别?从底层存储层面来说,
StorageType
的实现是作为一个整体序列化, 然后存储到数据 库. 而platon::db::Map
和platon::db::Array
则将容器的每一个元素序列化后, 作为 K/V 存储到数据库. 对于大规模数据,platon::db::Map
和platon::db::Array
性能 更好.在实现合约时, 应根据数据规模, 选择合适的存储结构.
RLP 序列化/反序列化 支持哪些 C++ 标准库类型?
支持以下 C++ 标准库类型:
- std::string
- std::vector
- std::map
- std::list
- std::array
- std::set
- std::pair
- std::tuple
自定义类型如何支持 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)); };
- 普通类型使用宏
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论