PlatON上的WASM智能合约开发(6)——进阶(代理调用的事件捕获与解析)

在《 PlatON上的WASM智能合约开发(5)——进阶(代理机制)》中向大家讲解了智能合约的代理机制,代理机制的使用必然会涉及到智能合约之间的跨合约调用(详见《 PlatON上的WASM智能合约开发(2)——跨合约调用》)。而事件是DApp进行链上合约、链下client交互的一种常见机制,在代理机制的使用过程中,交易型(PlatON WASM合约中通过ACTION修饰的方法)合约请求通常会涉及到业务合约事件的捕获与解析。当调用请求通过代理合约完成时,事件一般由实际的业务合约抛出。本文将通过相关案例,展示在合约代理机制的使用中,对合约事件的捕获解析,以及跨合约调用中对自定义参数的传递。PlatON在相关client SDK中提供了事件的解析机制,有关简单合约事件机制的讲解详见《 PlatON上的WASM智能合约开发(3)——事件机制》。

合约文件
dataStruct.h:

#pragma once
#include <platon/platon.hpp>
#include <string>
struct MyStruct{
    int i;
    std::string s;
    PLATON_SERIALIZE(MyStruct, (i)(s))

    MyStruct(){
        i = 0;
        s = "";
    }
};

说明:
platon wasm合约底层提供了序列化机制,通过PLATON_SERIALIZE修饰的自定义数据结构可以在跨合约调用时作为参数进行传递;

eventCalled.cpp:

#include <platon/platon.hpp>
#include <string>
#include "dataStruct.h"

class eventCalled: public platon::Contract{
public:
    PLATON_EVENT1(outCaller, std::string, int)

    ACTION void init(){}

    ACTION int outCall(MyStruct& ms){
        PLATON_EMIT_EVENT1(outCaller, "outside", ms.i);
        return 99;
    }
};

PLATON_DISPATCH(eventCalled, (init)(outCall))

说明:
实际完成业务操作的合约,outCall方法以自定义数据结构MyStruct作为参数;
在ACTION方法outCall执行时,会通过“outCaller”事件向client传递执行结果;

outCaller.cpp:

#include “dataStruct.h”
#include <platon/platon.hpp>
#include

class outCaller : public platon::Contract{
public:
ACTION void init(std::string& contractAddr){
auto rst = platon::make_address(contractAddr);
if (!rst.second){
//when the contractAddr is illegal, the proxy can not be used
platon::internal::platon_throw(“deploy failed!”);
}
else{
_contractAddr.self() = rst;
}
}

ACTION void setContract(std::string& contractAddr){
    auto rst = platon::make_address(contractAddr);
    if (!rst.second){
        //when the contractAddr is illegal, the proxy can not be used
        platon::internal::platon_throw("deploy failed!");
    }
    else{
        _contractAddr.self() = rst;
    }
}

CONST std::string getContract(){
    return _contractAddr.self().first.toString();
}

ACTION int Call(){
    if (!(_contractAddr.self().second))
    {
        platon::internal::platon_throw("this contract init failed!");
        return -1;
    }

    auto ms = MyStruct();
    auto result = platon::platon_call_with_return_value<int>(_contractAddr.self().first, (unsigned int)(0), (unsigned int)(0), "outCall", ms);
    return result.first;
}

platon::StorageType<"contract"_n, std::pair<platon::Address, bool>>            _contractAddr;

};

PLATON_DISPATCH(outCaller, (init)(setContract)(getContract)(Call))

说明:
该合约具备代理合约的部分功能,client通过调用Call接口,实际访问到业务合约的eventCalled::outCall方法;
合约调用时,将传递自定义数据结构MyStruct。

合约调用
合约调用基于client-sdk-python实现,若在案例测试使用过程中遇到任何问题,可联系cross团队。

true = True
false = False
#调用需要提供开发网账户
from_address = ‘…’

eventCalledAddr = ‘lat1npffkce3elp0ug80r85rz36y9gjkgevnculnyn’
eventCalled_abi = [{“anonymous”:false,“input”:[{“name”:“topic”,“type”:“string”},{“name”:“arg1”,“type”:“int32”}],“name”:“outCaller”,“topic”:1,“type”:“Event”},{“constant”:false,“input”:[],“name”:“init”,“output”:“void”,“type”:“Action”},{“baseclass”:[],“fields”:[{“name”:“i”,“type”:“int32”},{“name”:“s”,“type”:“string”}],“name”:“MyStruct”,“type”:“struct”},{“constant”:false,“input”:[{“name”:“ms”,“type”:“MyStruct”}],“name”:“outCall”,“output”:“int32”,“type”:“Action”}]

outCallerAddr = ‘lat1wtmjnquh4y08mpvrn0zd4c7hhyy0g9qpz58q8a’
outCaller_abi = [{“constant”:false,“input”:[{“name”:“contractAddr”,“type”:“string”}],“name”:“init”,“output”:“void”,“type”:“Action”},{“constant”:false,“input”:[],“name”:“Call”,“output”:“int32”,“type”:“Action”},{“constant”:false,“input”:[{“name”:“contractAddr”,“type”:“string”}],“name”:“setContract”,“output”:“void”,“type”:“Action”},{“constant”:true,“input”:[],“name”:“getContract”,“output”:“string”,“type”:“Action”}]

def testEventWasm():
w3 = Web3(HTTPProvider(“http://127.0.0.1:6789”))
platon = PlatON(w3)

eventCalled = platon.wasmcontract(address=eventCalledAddr, abi=eventCalled_abi,vmtype=1)

outCaller = platon.wasmcontract(address=outCallerAddr, abi=outCaller_abi,vmtype=1)
#调用outCaller::Call()
tx_hash = outCaller.functions.Call().transact({'from':from_address,'gas':1500000})
tx_events_receipt = platon.waitForTransactionReceipt(tx_hash)

#这里需要使用实际emit事件的合约获取events
topic_param = eventCalled.events.outCaller().processReceipt(tx_events_receipt)
print(topic_param)

说明:
调用需要使用outCaller、eventCalled两个合约的abi;
首先通过outCaller的abi调用outCaller::Call()方法;
然后等待捕获eventCalled的“outCaller”事件,通过eventCalled的abi解析事件内容;
调用需要连接开发网节点。

结果:


说明:
python SDK会将事件结果解析成一个AtrributeDict结构,其中’arg1’: 0在本案例中作为用户关心的处理结果返回。

总结
跨合约调用的事件机制比较简单,唯一需要注意的是在对事件进行解析时,需要使用实际抛出事件的合约abi才能正常解析。PlatON WASM合约对于需要在合约间调用时传递的参数,底层提供了序列化支持。

2 个赞

请问WASM合约的事件,可以将其中的参数标记为索引项吗?可以生成topic,方便后期filter中利用topic进行数据过滤。

@cross_planck

PLATON_EMIT_EVENT1(eventname, topic, other); 用xxx_EVENT1的话,第二个参数其实就是topic,所以不用特别的在wasm的代码中做设置。不过filter的话就需要在client端自己去设置了

抱歉,这句话没太理解,是指默认有两个topic吗(事件本身 + 第二个参数)?如果我想多定义几个参数作为topic该怎么写?做多可以定义几个topic(以太坊是四个:事件本身 + 三个索引项)?

比如ERC20 的这两个EVENT事件,每个事件有三个topic。如果我用WASM合约,应该怎么定义?image
监听到的log信息如下:
image

1636683849(1)

这几种形式都是支持的,第一个NAME是合约代码里面用的,代表事件本身。后面的TOPIC_TYPE1,2,3就是可以按需求定义的topic,应该就是你说的索引项。
PLATON_EVENT0(...)对应没有topic,PLATON_EVENT1(...)对应可以设置一个topic,以此类推~