In-depth explanation of contract development of blockchain alliance chain PlatONE Wasm

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 publictype method in the solidity contract. In the bcwasm library, the BCWASM_ABIexternal method is defined by macros. Through BCWASM_ABIthe 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 consttype 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_SERIALIZEmacro needs to be used in the structure, which provides a serialization/deserialization method for the structure type.

In the contract example below, a Student_tstructure 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 key
  • insert(key, value): When there is no content indexed by key in the map, insert the value indexed by key
  • update(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 setDatamethod inputs the student's name and age, and the getDatamethod 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_EVENTand BCWASM_EMIT_EVENTprovide 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 setDatathe 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 DeployedContractfor cross-contract calls. When you need to call other contracts in a contract, first initialize an DeployedContractexample 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);

DeployedContractThe 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 calleris the contract that initiated the call, and when used delegateCall(), the contract that initiated the call directly Pass its own callerto the target contract. For example, in the following two examples, in the first case, ContractB sees the calleraddress of ContractA; in the second case, ContractB sees the calleruser'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

  1. 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  

     

  2. The platone contract library does not define u32 and fixed-length array bytesN, which can be replaced by uint32_t and char[] arrays respectively.

  3. 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_CHARARRAYimplemented 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)

     

  4. 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>()

     

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324036854&siteId=291194637