Alliance chain PlatONE supports WASM virtual machine. This article explains the various functions of the contract through a series of code examples. You can learn more about how to write an application contract by learning these examples. Get PlatONE code
Contract class
The Contract class is the base class of contracts provided by the bcwasm library, and contracts developed by users must be derived from this class. init()
A virtual function is defined in the Contract class, and the user contract needs to implement this init()
function. This function is executed when the contract is first released, and it is called only once. This method is similar to the constructor in the solidity contract.
Note: The method must be implemented as a type so that the contract can call this function to initialize the contract data when it is deployed. init()
public
#include <bcwasm/bcwasm.hpp>
namespace my_namespcase {
class my_contract : public bcwasm::Contract
{
public:
my_contract(){}
/// 实现父类: bcwasm::Contract 的虚函数
/// 该函数在合约首次发布时执行,仅调用一次
void init()
{
/* 做一些初始化操作 */
}
};
}
contract external method
The external method of the contract refers to the interface that the contract can call externally. The function is similar to the public
type method in the solidity contract. In the bcwasm library, the BCWASM_ABI
external method is defined by macros. Through BCWASM_ABI
the declared method, it can be called by rpc message outside the contract, and can also be called by other contracts.
#include <bcwasm/bcwasm.hpp>
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)
On-chain storage interface
The built-in library of the Wasm contract provides a setState()
method for data persistence. Data persistence can be achieved by calling bcwasm::setState()
functions, and query calls can be made accordingly bcwasm::getState()
.
In the example contract below, there are two interfaces for external calls setData(char* data)
and getData()
. These two methods call bcwasm::setState()
, respectively bcwasm::getState()
, to realize the on-chain persistence and query of data.
#include <bcwasm/bcwasm.hpp>
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 method
The const
type method in the contract provides read-only operations on the contract state. The function declared by this type cannot modify the contract data, and is generally used to query the contract's on-chain data. In the code below, getData()
the const method is used to query data.
const char* getData() const{
std::string m_data;
bcwasm::getState("DataKey", m_data);
// 读取合约数据并返回
return m_data.c_str();
}
Struct、Map
Struct structure
The structure syntax rules are consistent with C++, but if the structure data needs to be persisted on the chain, a BCWASM_SERIALIZE
macro needs to be used in the structure, which provides a serialization/deserialization method for the structure type.
In the contract example below, a Student_t
structure type is defined, and setData()
the data is persisted to the chain through the contract interface, and then the getData()
data can be queried through methods.
#include <bcwasm/bcwasm.hpp>
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)
Map
bcwasm provides the encapsulation of the map type. When defining the map structure, you need to specify the name of the map, the type of the key, and the type of the value.
char mapName[] = "students";
bcwasm::db::Map<mapName, std::string, Student_t> students;
The map structure supports the following APIs:
find(key)
: Find value by keyinsert(key, value)
: When there is no content indexed by key in the map, insert the value indexed by keyupdate(key, value)
: When the content indexed by key already exists in the map, update the value corresponding to the key
The example contract below defines a map to save the student's name and age information, with the student's name as the key as the index, where the setData
method inputs the student's name and age, and the getData
method queries the student's age according to the name.
#include <bcwasm/bcwasm.hpp>
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<mapName, std::string, Student_t> 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
Event allows us to conveniently use PlatONE's logging infrastructure. We can monitor Events in the dapp, and when an Event is generated in the contract, the relevant parameters will be stored in the transaction log. These logs are associated with addresses and are written into the blockchain, and events generated by a transaction can be queried through transaction receipts.
Macros BCWASM_EVENT
and BCWASM_EMIT_EVENT
provide direct support for contract events. The usage methods are as follows:
/// 定义Event.
/// BCWASM_EVENT(eventName,arguments...)
BCWASM_EVENT(setData,const char *,const int64_t)
/// 触发Event
BCWASM_EMIT_EVENT(setData,name,age);
We add an Event event to the sample contract, and each time it is called setData()
, the Event event is triggered. The sample contract code is as follows:
#include <bcwasm/bcwasm.hpp>
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<mapName, std::string, Student_t> 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)
After adding the Event, we call setData
the contract again, and then query the receipt of the transaction, as shown below.
{
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
}
In the logs field of Receipt is the data we generate through Event, where the meaning of the main fields is:
- address: the contract address that generated the Event
- blockHash: The block hash of the transaction that generated the event
- blockNumber: the block number of the transaction that generated the event
- data: The RLP encoding of the Event parameter, which is the RLP encoding of [name, age] in the above contract example
- topics: the hash value of the event name, in the contract example above, the
"setData"
hash value of the string
Cross-contract call
The bcwasm library provides classes DeployedContract
for cross-contract calls. When you need to call other contracts in a contract, first initialize an DeployedContract
example with the target contract address, and then call the corresponding method, as shown below:
// 调用目的地址: "0x07894a9f9edffe4b73eb8928f76ee2993039e4d7"
// 调用的方法: setData(name,age)
bcwasm::DeployedContract regManagerContract("0x07894a9f9edffe4b73eb8928f76ee2993039e4d7");
char name[]= "name";
int64_t age = 18;
regManagerContract.call("setData", name, age);
DeployedContract
The following calling methods are provided:
// 无需返回值调用
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()
Both delegateCall()
can be used to call a contract, but there is a difference from the perspective of the called target contract. When used, the call()
caller seen by the called contract caller
is the contract that initiated the call, and when used delegateCall()
, the contract that initiated the call directly Pass its own caller
to the target contract. For example, in the following two examples, in the first case, ContractB sees the caller
address of ContractA; in the second case, ContractB sees the caller
user's address.
1. user ----> ContractA --call()--> ContractB
2. user ----> ContractA --delegateCall()--> ContractB
Register the cns contract in the initialization method
PlatONE provides the CNS service function in the system contract, which can register the contract in the system contract, so as to use the contract name version to call the contract without using the address. init()
The contract can be directly registered to the system contract in the initialization method of the contract, so as to use the convenient functions of the CNS contract.
It can be achieved by calling the init()
method of the cnsManager contract in the method, and it is necessary to pay attention to the format that the cnsRegisterFromInit(name,version)
contract version must be ."x.x.x.x"
void init()
{
DeployedContract reg("0x0000000000000000000000000000000000000011");
reg.call("cnsRegisterFromInit", "name", "1.0.0.0");
}
hash()
The bcwasm library provides a hashing method consistent with Ethereum sha3()
, which is used as follows:
std::string msg = "hello";
bcwasm::h256 hash = bcwasm::sha3(msg);
ecrecover()
ecrecover()
The function provides the function of recovering the signer's address based on the original hash and signature. The usage method is as follows:
// 针对字符串"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()
- caller(): Returns the caller information. If contract A calls contract B through the call() method, then the caller is the address of contract A
- origin(): Returns the address of the originator of the call, regardless of the call situation between contracts, this function always returns the address of the sender of the original transaction
- address(): returns the address of the current contract
Other considerations
-
The current contract external interface only supports the following data types:
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
-
The platone contract library does not define u32 and fixed-length array bytesN, which can be replaced by uint32_t and char[] arrays respectively.
-
When implementing the query method of the external interface of the contract, if the function returns a string (for example, by calling the c_str() method of string), you need to apply for (malloc) a new piece of memory inside the function, and copy the string to the This new memory, because the memory is managed by the BCWasm virtual machine, does not have a memory leak problem. When returning a string type, it can be
RETURN_CHARARRAY
implemented using a macro, which is defined as follows#define RETURN_CHARARRAY(src,size) \ do \ { \ char *buf = (char *)malloc(size); \ memset(buf,0,size); \ strcpy(buf,src); \ return buf; \ } \ while(0)
-
The u256 type conversion string type in the built-in library of the wasm contract needs to be called as follows:
u256value.convert_to<std::string>()