主页 > 华为无法更新imtoken > 区块链联盟链PlatONE Wasm合约发展深入详解
区块链联盟链PlatONE Wasm合约发展深入详解
联盟链 PlatONE 支持 WASM 虚拟机。本文通过一系列代码示例说明了合约的各种功能。用户可以学习这些示例区块链合约地址查询,以深入了解如何编写应用程序合约。 PlatONE代码获取
合约类
Contract类是bcwasm库提供的合约基类,用户开发的合约必须派生自该类。 Contract类中定义了一个init()虚函数,用户合约需要实现init()函数。该函数在合约首次发布时执行,只调用一次。该方法类似于solidity合约中的构造函数。
注意:init()方法必须实现为公共类型区块链合约地址查询,以便合约在部署时调用该函数初始化合约数据。
#include
namespace my_namespcase {
class my_contract : public bcwasm::Contract
{
public:
my_contract(){}
/// 实现父类: bcwasm::Contract 的虚函数
/// 该函数在合约首次发布时执行,仅调用一次
void init()
{
/* 做一些初始化操作 */
}
};
}
合同外部方法
合约的外部方法是指合约可以对外调用的接口。功能类似于solidity合约中的public类型方法。在 bcwasm 库中,BCWASM_ABI 宏用于定义外部方法。 BCWASM_ABI声明的方法可以被合约外的rpc消息调用,也可以被其他合约调用。
#include
namespace my_namespcase {
class my_contract : public bcwasm::Contract
{
public:
my_contract(){}
/// 实现父类: bcwasm::Contract 的虚函数
/// 该函数在合约首次发布时执行,仅调用一次
void init()
{
/* 做一些初始化操作 */
}
void setData(char * data){
/* 修改合约数据 */
}
};
}
// 外部方法
BCWASM_ABI(my_namespcase::my_contract:setData)
链上存储接口
Wasm 合约的内置库提供了数据持久化的 setState() 方法。数据持久化可以通过调用bcwasm::setState()函数实现,bcwasm::getState()也可以相应调用。
在下面的示例合约中,有两个接口用于外部调用 setData(char* data) 和 getData()。这两个方法分别调用bcwasm::setState()和bcwasm::getState()实现数据的链上持久化和查询。
#include
namespace my_namespcase {
class my_contract : public bcwasm::Contract
{
public:
void init(){}
void setData(char * data){
std::string m_data(data);
bcwasm::setState("DataKey", m_data);
}
const char* getData() const{
std::string m_data;
bcwasm::getState("DataKey", m_data);
return m_data.c_str();
}
};
}
// 外部方法
BCWASM_ABI(my_namespcase::my_contract, setData)
BCWASM_ABI(my_namespcase::my_contract, getData)
常量方法
合约中的 const 类型方法提供对合约状态的只读操作。该类型声明的函数不能修改合约数据,一般用于查询链上合约数据。在下面的代码中,getData() 是一个用于查询数据的 const 方法。
const char* getData() const{
std::string m_data;
bcwasm::getState("DataKey", m_data);
// 读取合约数据并返回
return m_data.c_str();
}
Struct、MapStruct结构
结构体语法规则与C++一致,但如果用于将结构体数据持久化在链上,则需要在结构体中使用BCWASM_SERIALIZE宏,该宏提供了结构体类型的序列化/反序列化方法。
在下面的合约示例中,定义了一个Student_t结构类型,通过合约接口setData()将数据持久化到链上,然后可以通过getData()方法查询数据。
#include
namespace my_namespcase {
struct Student_t
{
std::string name; // 姓名
int64_t age; // 年龄
BCWASM_SERIALIZE(Student_t, (name)(age));
};
class my_contract : public bcwasm::Contract
{
public:
void init(){}
void setData(char * name, int64_t age){
Student_t stu;
stu.name = std::string (name);
stu.age = age;
bcwasm::setState("DataKey", stu);
}
const char* getData() const{
Student_t stu;
bcwasm::getState("DataKey", stu);
std::stringstream ret;
ret << "stu.name: " << stu.name << ", stu.age: " << stu.age;
// 读取合约数据并返回
return ret.str().c_str();
}
};
}
// 外部方法
BCWASM_ABI(my_namespcase::my_contract, setData)
BCWASM_ABI(my_namespcase::my_contract, getData)
地图
bcwasm 提供了对地图类型的封装。定义map结构时,需要指定map的名称、key的类型、value的类型。
char mapName[] = "students";
bcwasm::db::Map students;
地图结构支持以下 API:
下面的示例合约中定义了一个map来保存学生的姓名和年龄信息,以学生的姓名为key作为索引,其中setData方法输入学生的姓名和年龄,getData方法查询学生的年龄取决于名字。
#include
namespace my_namespcase {
struct Student_t
{
std::string name; // 姓名
int64_t age; // 年龄
BCWASM_SERIALIZE(Student_t, (name)(age));
};
// 定义一个map,保存学生姓名、年龄信息,以学生姓名为key作为索引
char mapName[] = "students";
bcwasm::db::Map students;
class my_contract : public bcwasm::Contract
{
public:
void init(){}
void setData(char * name, int64_t age){
Student_t stu;
stu.name = std::string (name);
stu.age = age;
Student_t *stu_p = students.find(std::string(name));
if (stu_p == nullptr){
students.insert(stu.name, stu);
} else{
students.update(stu.name, stu);
}
}
const char* getData(char* name) const{
Student_t *stu = students.find(std::string(name));
if (stu == nullptr){
return (std::string("no such student")).c_str();
}else{
std::stringstream ret;
ret << "stu.name: " << stu->name << ", stu.age: " << stu->age;
return ret.str().c_str();
}
}
};
}
// 外部方法
BCWASM_ABI(my_namespcase::my_contract, setData)
BCWASM_ABI(my_namespcase::my_contract, getData)
事件
Event 让我们可以方便地使用 PlatONE 的日志记录基础设施。我们可以在 dapp 中监控 Event,当合约中产生 Event 时,相关参数会存储在交易日志中。这些日志与地址相关联并写入区块链,交易产生的事件可以通过交易回执查询。
宏 BCWASM_EVENT 和 BCWASM_EMIT_EVENT 为合约事件提供直接支持。使用方法如下:
/// 定义Event.
/// BCWASM_EVENT(eventName,arguments...)
BCWASM_EVENT(setData,const char *,const int64_t)
/// 触发Event
BCWASM_EMIT_EVENT(setData,name,age);
我们在示例合约中添加一个 Event 事件。每次调用 setData() 时,都会触发 Event 事件。示例合约代码如下:
#include
namespace my_namespcase {
struct Student_t
{
std::string name; // 姓名
int64_t age; // 年龄
BCWASM_SERIALIZE(Student_t, (name)(age));
};
// 定义一个map,保存学生姓名、年龄信息,以学生姓名为key作为索引
char mapName[] = "students";
bcwasm::db::Map students;
class my_contract : public bcwasm::Contract
{
public:
void init(){}
// 定义Event
BCWASM_EVENT(setData,const char*,int64_t)
void setData(char * name, int64_t age){
Student_t stu;
stu.name = std::string(name);
stu.age = age;
Student_t *stu_p = students.find(std::string(name));
if (stu_p == nullptr){
students.insert(stu.name, stu);
} else{
students.update(stu.name, stu);
}
/// 触发Event
BCWASM_EMIT_EVENT(setData,name,age);
}
const char* getData(char * name) const{
Student_t *stu = students.find(std::string(name));
if (stu == nullptr){
return (std::string("no such student")).c_str();
}else{
std::stringstream ret;
ret << "stu.name: " << stu->name << ", stu.age: " << stu->age;
return ret.str().c_str();
}
}
};
}
// 外部方法
BCWASM_ABI(my_namespcase::my_contract, setData)
BCWASM_ABI(my_namespcase::my_contract, getData)
添加Event后,我们再次调用setData合约,然后查询交易的Receipt,如下图。
{
blockHash: "0xd3324a86bb4c2a9f99592ea16c02bddae6ced421c0170a07f781fb9dfa7b1d8c",
blockNumber: 77,
contractAddress: null,
cumulativeGasUsed: 449872,
from: "0x61eaf416482341e706ff048f20491cf280bc29d6",
gasUsed: 449872,
logs: [{
address: "0x07894a9f9edffe4b73eb8928f76ee2993039e4d7",
blockHash: "0xd3324a86bb4c2a9f99592ea16c02bddae6ced421c0170a07f781fb9dfa7b1d8c",
blockNumber: 77,
data: "0xc785676578696e1c",
logIndex: 0,
removed: false,
topics: ["0xd20950ab1def1a5df286475bfce09dc88d9dcba71bab52f01965650b43a7ca8e"],
transactionHash: "0xa4735b9dbf93f0f8d7831f893270ff8a42244141455ed308fd985b90ee9bc3f5",
transactionIndex: 0
}],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000800000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000",
status: "0x1",
to: "0x07894a9f9edffe4b73eb8928f76ee2993039e4d7",
transactionHash: "0xa4735b9dbf93f0f8d7831f893270ff8a42244141455ed308fd985b90ee9bc3f5",
transactionIndex: 0
}
Receipt的logs字段中是我们通过Event生成的数据,主要字段的含义是:
跨合约调用
bcwasm 库为跨合约调用提供类 DeployedContract。当需要调用合约中的其他合约时,先用目标合约地址初始化一个DeployedContract实例,然后调用对应的方法,如下图:
// 调用目的地址: "0x07894a9f9edffe4b73eb8928f76ee2993039e4d7"
// 调用的方法: setData(name,age)
bcwasm::DeployedContract regManagerContract("0x07894a9f9edffe4b73eb8928f76ee2993039e4d7");
char name[]= "name";
int64_t age = 18;
regManagerContract.call("setData", name, age);
DeployedContract 提供以下调用方法:
// 无需返回值调用
void call("funcName", arguments...);
void delegateCall("funcName", arguments...);
// string类型返回值
std::string callString("funcName", arguments...)
std::string delegateCallString("funcName", arguments...)
// Int64类型返回值
int64_t callInt64("funcName", arguments...)
int64_t delegateCallInt64("funcName", arguments...)
call() 和delegateCall() 都可以用来调用合约,但是从被调用的目标合约的角度来看是有区别的。使用 call() 时,被调用合约看到的调用者是发起调用的调用者,使用delegateCall() 时,调用合约直接将自己的调用者传递给目标合约。比如下面两个例子,第一种情况,ContractB看到的调用者是ContractA的地址;第二种情况,ContractB看到的调用者就是用户的地址。
1. user ----> ContractA --call()--> ContractB
2. user ----> ContractA --delegateCall()--> ContractB
在初始化方法中注册cns合约
PlatONE在系统合约中提供CNS服务功能,可以在系统合约中注册合约,从而在不使用地址的情况下使用合约名版本调用合约。合约可以在合约的初始化方法init()中直接注册到系统合约中,以使用CNS合约的便捷功能。
可以通过在init()方法中调用cnsManager合约的cnsRegisterFromInit(name,version)方法来实现。需要注意的是,合约版本必须是“x.x.x.x”的格式。
void init()
{
DeployedContract reg("0x0000000000000000000000000000000000000011");
reg.call("cnsRegisterFromInit", "name", "1.0.0.0");
}
哈希()
bcwasm库提供了与以太坊一致的哈希方法sha3(),使用如下:
std::string msg = "hello";
bcwasm::h256 hash = bcwasm::sha3(msg);
ecrecover()
ecrecover() 函数提供了根据原始哈希和签名恢复签名者地址的功能。使用方法如下:
// 针对字符串"hello"的签名
std::string sig = "4949eb47832d8a90c8c94b57de49d11b031fcd6d6dcb18c198103d2d431e2edf07be6c3056fe054ad6d1f62a24a509426a1c76687708ab684ad609ae879399fa00";
// 签名原文
std::string msg = "hello";
// 首先求出签名原文的哈希
bcwasm::h256 hash = bcwasm::sha3(msg);
// 通过ecrecover恢复出签名人地址
bcwasm::h160 addr = bcwasm::ecrecover(hash.data(), bcwasm::fromHex(sig).data());
caller()、origin()、address()的其他说明 目前合约对外接口只支持以下数据类型:
char/char* /const char*/char[]
unsigned __int128/__int128/uint128_t
unsigned long long/uint64_t
unsigned long/uint32_t
unsigned short/uint16_t
unsigned char/uint8_t
long long/int64_t
long/int32_t/int
short/int16_t
char/int8_t
void
2. platone 合约库没有定义 u32 和定长数组 bytesN。目前可以使用 uint32_t 和 char[] 数组代替。
3. 在实现合约对外接口的查询方法时,如果函数返回的是字符串(例如调用了字符串的c_str()方法),需要申请(malloc)一个函数内部的新内存。 , 并将字符串复制到这个新内存中。由于内存由 BCWasm 虚拟机管理,因此不存在内存泄漏问题。返回字符串类型时,可以使用RETURN_CHARARRAY宏,定义如下:
#define RETURN_CHARARRAY(src,size) \
do \
{ \
char *buf = (char *)malloc(size); \
memset(buf,0,size); \
strcpy(buf,src); \
return buf; \
} \
while(0)
4.wasm合约内置库中的u256类型转换字符串类型需要调用如下:
u256value.convert_to()