PlatON上的WASM智能合约开发(8)——进阶(内置数据结构)

合约一般可视为Dapp的“后台”,不可避免将对一些业务相关的数据进行记录。本篇针对可能常用的三类结构(platon::StorageType、platon::db::Map、platon::db::MultiIndex)进行示例讲解。其中platon::StorageType是最为灵活的使用方式,支持用户自定义数据结构,platon::db::Map的用法与std::map较为类似,platon::db::MultiIndex支持创建多索引的数据结构,便于根据不同的索引进行查询。由于链上存储有其独有的特征,这三类数据结构在操作性能上可能存在一定的差异,有机会笔者将在后续系列文章中将对此进行讲解。

合约代码示例
WasmDataStruct.h代码
#pragma once

#include <platon/platon.hpp>
#include <string>
#include <vector>

struct UserDefinedData{
    platon::u128    uID;
    std::string     uName;

    std::string     uContent;

    PLATON_SERIALIZE(UserDefinedData, (uID)(uName)(uContent))

    platon::u128 UserID() const { return uID; }
    std::string UserName() const { return uName; }
};

class WasmDataStruct{
    //map
    platon::db::Map<"UserDataMap"_n, platon::u128, UserDefinedData> _mUserMap;

    //multiIndex table
    platon::db::MultiIndex<
        "UserTable"_n, UserDefinedData,
        platon::db::IndexedBy<"id"_n, platon::db::IndexMemberFun<UserDefinedData, platon::u128, &UserDefinedData::UserID,
        platon::db::IndexType::UniqueIndex>>,
        platon::db::IndexedBy<"name"_n, platon::db::IndexMemberFun<UserDefinedData, std::string, &UserDefinedData::UserName,
        platon::db::IndexType::NormalIndex>>>
        
        _mUser_table;

    //StorageType
    platon::StorageType<"UserData"_n, std::map<platon::u128, UserDefinedData>>      _mUser_Data;

public:
    ACTION void init();

    ACTION void AddUser(const UserDefinedData& udd);

    ACTION void ModifyUserInfo(const UserDefinedData& udd);

    ACTION void UserErase(const platon::u128& uID);

    ACTION void Clear();

    CONST std::vector<UserDefinedData> getUserFromTable_ID(const platon::u128& uID);
    CONST std::vector<UserDefinedData> getUserFromTable_Name(const std::string& uName);

    CONST std::vector<UserDefinedData> getUserFromMap(const platon::u128& uID);

    CONST std::vector<UserDefinedData> getUserFromStorageType(const platon::u128& uID);
};

PLATON_DISPATCH(WasmDataStruct, (init)(AddUser)(ModifyUserInfo)(UserErase)(Clear)(getUserFromTable_ID)(getUserFromTable_Name)(getUserFromMap)(getUserFromStorageType))

WasmDataStruct.cpp代码

#include "WasmDataStruct.h"

void WasmDataStruct::init(){
    
}

void WasmDataStruct::AddUser(const UserDefinedData& udd){
    _mUserMap[udd.uID] = udd;
    _mUser_Data.self()[udd.uID] = udd;

    _mUser_table.emplace([&](auto& userTable) {
        userTable = udd;
        });
}

void WasmDataStruct::ModifyUserInfo(const UserDefinedData& udd){
    //_mUserMap
    if (_mUserMap.contains(udd.uID))
    {
        _mUserMap[udd.uID] = udd;
    }

    //_mUser_Data
    auto udItr = _mUser_Data.self().find(udd.uID);
    if (udItr != _mUser_Data.self().end())
    {
        udItr->second = udd;
    }

    //_mUser_table
    auto uTableItr = _mUser_table.find<"id"_n>(udd.uID);
    if (uTableItr != _mUser_table.cend())
    {
        _mUser_table.modify(uTableItr, [&](auto& userData) {
            userData = udd;
            });
    }
}

void WasmDataStruct::UserErase(const platon::u128& uID){
    //_mUserMap
    if (_mUserMap.contains(uID))
    {
        _mUserMap.erase(uID);
    }

    //_mUser_Data
    auto udItr = _mUser_Data.self().find(uID);
    if (udItr != _mUser_Data.self().end())
    {
        _mUser_Data.self().erase(udItr);
    }

    //_mUser_table
    auto uTableItr = _mUser_table.find<"id"_n>(uID);
    if (uTableItr != _mUser_table.cend())
    {
        _mUser_table.erase(uTableItr);
    }
}

void WasmDataStruct::Clear(){
    //the clear of db::Map and db::MultiIndex are Some trouble
    std::vector<platon::u128> idVec;
    for (auto usItr = _mUser_Data.self().begin(); usItr != _mUser_Data.self().end(); ++usItr)
    {
        idVec.push_back(usItr->first);
    }

    //normal map
    _mUser_Data.self().clear();

    //db::Map
    for (auto idItr = idVec.begin(); idItr != idVec.end(); ++idItr)
    {
        _mUserMap.erase(*idItr);
    }

    //db::MultiIndex
    auto tableItr = _mUser_table.cbegin();
    while (tableItr != _mUser_table.cend())
    {
        auto tempItr = tableItr;
        ++tableItr;
        
        _mUser_table.erase(tempItr);
    }
}

std::vector<UserDefinedData> WasmDataStruct::getUserFromTable_ID(const platon::u128& uID){
    std::vector<UserDefinedData> udVec;

    auto uTableItr = _mUser_table.find<"id"_n>(uID);
    if (uTableItr != _mUser_table.cend())
    {
        
        udVec.push_back(*uTableItr);
    }

    //if not found, return []
    return udVec;
}

std::vector<UserDefinedData> WasmDataStruct::getUserFromTable_Name(const std::string& uName){
    std::vector<UserDefinedData> udVec;
    //Here's where it gets more confusing, maybe set parameters in get_index is better
    auto normalIndexes = _mUser_table.get_index<"name"_n>();
    for (auto itemItr = normalIndexes.cbegin(uName); itemItr != normalIndexes.cend(uName); ++itemItr)
    {
        udVec.push_back(*itemItr);
    }

    return udVec;
}

std::vector<UserDefinedData> WasmDataStruct::getUserFromMap(const platon::u128& uID){
    std::vector<UserDefinedData> udVec;

    if (_mUserMap.contains(uID))
    {
        udVec.push_back(_mUserMap[uID]);
    }

    return udVec;
}

std::vector<UserDefinedData> WasmDataStruct::getUserFromStorageType(const platon::u128& uID){
    std::vector<UserDefinedData> udVec;

    auto udItr = _mUser_Data.self().find(uID);
    if (udItr != _mUser_Data.self().end())
    {
        udVec.push_back(udItr->second);
    }

    return udVec;
}

说明:
1、本示例分别定了三种数据类型(_mUserMap、_mUser_table、_mUser_Data),用于存放UserDefinedData;
2、添加元素:AddUser,同时将数据写入三种数据类型中,可在.cpp实现中看到三种添加方式的区别,db::MultiIndex区别较大,需要通过lambda操作来进行元素构造添加;在本示例中,添加元素故意没有进行判重操作,在后续调用测试中,可见db::Map、StorageType中的相关元素都已经被覆盖,但db::MultiIndex中本身具备了判重的能力;
3、修改元素:ModifyUserInfo,同时修改三种数据类型中的相同元素,在操作上db::Map与用户在StorageType中自定义的Map类似,但db::MultiIndex区别较大,需要通过lambda操作来进行修改;
4、删除元素:UserErase,删除操作差别不大,值得一提的是db::Map没有提供相应的迭代器,可通过key直接删除;
5、清空元素:Clear,清空操作db::Map比较麻烦,需要先将所有的key存下来,具体做法可参见源码;
6、查询元素:从db::Map、StorageType中查询较为简单。db::MultiIndex支持多索引查询,其中通过唯一键(platon::db::IndexType::UniqueIndex)查询也比较简单,值得一提的是通过普通键进行查询,需要先通过get_index<“keyname”_n>()获取所有元素(内部做了顺序处理),然后通过cbigin(name)查找所有key匹配的元素,最终将返回多个普通键值为name的元素,详见.cpp中第104至114行。

Client代码示例
C端代码(python)

from client_sdk_python import Web3, HTTPProvider, WebsocketProvider
from client_sdk_python.eth import PlatON
from client_sdk_python import utils
from client_sdk_python.utils import contracts

import json

ContractAddr = 'lat1ddydcvxtj0wus3ctt9pkgwjdl5hsjx099ccd3a'

clientAccount = 'your account for testNet'
devIP = 'ip of testNet node'

def addItem():
    fp = open('./PlatON/config/WasmDataStruct.abi.json')
    Contract_abi = json.load(fp)
    #print(breakingNews_abi)

    w3 = Web3(HTTPProvider(devIP))
    platon = PlatON(w3)
    hello = platon.wasmcontract(address=ContractAddr, abi=Contract_abi, vmtype=1)

    #print(hello.functions.getUserFromTable_ID(73).call())

    tx_receipt = hello.functions.AddUser([1, 'jason', 'Hello world']).transact({'from':clientAccount,'gas':1500000})
    receipt_info = platon.waitForTransactionReceipt(tx_receipt)
    print(receipt_info)

    tx_receipt = hello.functions.AddUser([2, 'jason', 'Hello Platon']).transact({'from':clientAccount,'gas':1500000})
    receipt_info = platon.waitForTransactionReceipt(tx_receipt)
    print(receipt_info)

    tx_receipt = hello.functions.AddUser([3, 'cross', 'Hello Cross']).transact({'from':clientAccount,'gas':1500000})
    receipt_info = platon.waitForTransactionReceipt(tx_receipt)
    print(receipt_info)

    #ID's all ready exist
    tx_receipt = hello.functions.AddUser([3, 'none', 'Hello None']).transact({'from':clientAccount,'gas':1500000})
    receipt_info = platon.waitForTransactionReceipt(tx_receipt)
    print(receipt_info)

def getItems():
    fp = open('./PlatON/config/WasmDataStruct.abi.json')
    Contract_abi = json.load(fp)
    #print(breakingNews_abi)

    w3 = Web3(HTTPProvider(devIP))
    platon = PlatON(w3)
    hello = platon.wasmcontract(address=ContractAddr, abi=Contract_abi, vmtype=1)

    print(hello.functions.getUserFromTable_ID(1).call())
    print(hello.functions.getUserFromTable_ID(2).call())
    
    print(hello.functions.getUserFromTable_ID(3).call())
    print(hello.functions.getUserFromMap(3).call())
    print(hello.functions.getUserFromStorageType(3).call())

    #call from name by table
    print(hello.functions.getUserFromTable_Name('jason').call())

def modifyItem():
    fp = open('./PlatON/config/WasmDataStruct.abi.json')
    Contract_abi = json.load(fp)
    #print(breakingNews_abi)

    w3 = Web3(HTTPProvider(devIP))
    platon = PlatON(w3)
    hello = platon.wasmcontract(address=ContractAddr, abi=Contract_abi, vmtype=1)

    tx_receipt = hello.functions.ModifyUserInfo([2, 'jason', 'Hello PlatON Cross']).transact({'from':clientAccount,'gas':1500000})
    receipt_info = platon.waitForTransactionReceipt(tx_receipt)
    print(receipt_info)

    print(hello.functions.getUserFromTable_ID(2).call())

def ClearData():
    fp = open('./PlatON/config/WasmDataStruct.abi.json')
    Contract_abi = json.load(fp)
    #print(breakingNews_abi)

    w3 = Web3(HTTPProvider(devIP))
    platon = PlatON(w3)
    hello = platon.wasmcontract(address=ContractAddr, abi=Contract_abi, vmtype=1)

    tx_receipt = hello.functions.Clear().transact({'from':clientAccount,'gas':1500000})
    receipt_info = platon.waitForTransactionReceipt(tx_receipt)
    print(receipt_info)

    print(hello.functions.getUserFromTable_ID(1).call())
    print(hello.functions.getUserFromTable_ID(2).call())
    print(hello.functions.getUserFromTable_ID(3).call())

#addItem()
getItems()
#modifyItem()
#ClearData()

调用结果
call addItem:
1
说明:
1、第四次调用使用了与第三次效用相同的主键,本文示例故意没有在合约的添加元素操作中进行判重操作,这主要是为了体现三种结构的差异,结果将在getItems中呈现。

call getItems:
2
说明:
1、在.py第53~55行可以看到,调用查询主键为3的元素,三种数据类型返回的值不一样,这是由于db::MultiIndex自带判重的操作;

call modifyItem:
3
说明:
1、操作成功;

call ClearData:
4
说明:
1、操作成功。

总结
本文对wasm合约内置的几种典型数据结构进行了简单讲解,如在开发中遇到问题,可联系cross团队。

优秀,欢迎社区的伙伴一起贡献教程,不限视频或文档形式。