从本篇开始,Cross技术团队将开始对PlatON上WASM智能合约的一些进阶使用进行讲解。
经编译部署后的区块链智能合约本质上是记录在链上的一段字节码,因为区块链不可篡改的特点,合约部署后实际上是“永远”保存在了链上,而智能合约作为DApp的“后台”,在使用过程中,由于应用功能的迭代更新,其更改迭代的需求几乎是必然的。目前,PlatON上的WASM合约尚未公开直接进行合约在线更新的底层机制,本篇内容将提供一种可应用于PlatON上WASM智能合约在线升级的代理机制,有关合约在线升级其他方面的内容,将在后续文章中进行讲解。
示例说明
本文示例由三个合约组成:contractProxy(代理合约)、calcContract(业务合约1)、calcContract2(业务合约2)组成。
contractProxy
contractProxy合约的功能分为两部分:一是完成代理管理的相关功能,二是对业务接口的调用,合约代码如下:
#include <platon/platon.hpp>
#include <string>
//this proxy instance can be proxy for only one contract at a time
//real contract shall be deployed before this proxy is deployed
CONTRACT calc_contract_proxy : platon::Contract{
public:
PLATON_EVENT1(incalcAdd, std::string, int)
PLATON_EVENT1(inmakeSum, std::string, int)
//the input param is for Security considerations
ACTION void init(std::string& contractAddr){
auto rst = platon::make_address(contractAddr);
//when the contractAddr is illegal, the proxy can not be used
if (!rst.second){
platon::internal::platon_throw("deploy failed!");
}
else{
_contractAddr.self() = rst;
}
}
//contract can be altered. But the caller must be current contract
ACTION bool RegisterContract(std::string& contractAddr){
//can't be called when owner is illegal
if (!_contractAddr.self().second){
platon::internal::platon_throw("this contract init failed!");
return false;
}
platon::Address senderAddr = platon::platon_caller();
//if caller is the owner, replace the owner address
if (senderAddr != _contractAddr.self().first){
return false;
}
auto result = platon::make_address(contractAddr);
if (!result.second){
return false;
}
_contractAddr.self() = result;
return true;
}
//the following methods are for represented contracts
//the interfaces are agree with the represented contracts
ACTION std::pair<int, bool> calcAdd(int a, int b){
//can't be called when owner is illegal
if (!(_contractAddr.self().second)){
platon::internal::platon_throw("this contract init failed!");
return std::pair<int, bool>(0, false);
}
//make call to real contract
auto result = platon::platon_call_with_return_value<int>(_contractAddr.self().first, (unsigned int)(0), (unsigned int)(0), "calcAdd", a, b);
PLATON_EMIT_EVENT1(incalcAdd, "calcAdd" , result.first);
return result;
}
CONST std::pair<int, bool> const_calcAdd(int a, int b){
//can't be called when owner is illegal
if (!(_contractAddr.self().second)){
platon::internal::platon_throw("this contract init failed!");
return std::pair<int, bool>(0, false);
}
//make call to real contract
auto result = platon::platon_call_with_return_value<int>(_contractAddr.self().first, (unsigned int)(0), (unsigned int)(0), "const_calcAdd", a, b);
return result;
}
ACTION std::pair<int, bool> makeSum(std::vector<int>& eles){
//can't be called when owner is illegal
if (!(_contractAddr.self().second)){
platon::internal::platon_throw("this contract init failed!");
return std::pair<int, bool>(0, false);
}
//make call to real contract
unsigned int len = eles.size();
if (0 == len){
PLATON_EMIT_EVENT1(inmakeSum, "makeSum" , 0);
return std::pair<int, bool>(0, true);
}
else{
//call methods
auto result = platon::platon_call_with_return_value<int>(_contractAddr.self().first, (unsigned int)(0), (unsigned int)(0), "makeSum", eles);
PLATON_EMIT_EVENT1(inmakeSum, "makeSum" , result.first);
return result;
}
}
private:
platon::StorageType<"contract"_n, std::pair<platon::Address, bool>> _contractAddr;
};
PLATON_DISPATCH(calc_contract_proxy, (init)(RegisterContract)(calcAdd)(const_calcAdd)(makeSum))
接口说明:
init:初始化,部署时调用,需要输入正常的lat地址(最好是业务合约,也可以是普通地址);
代理管理相关接口:
RegisterContract:更改业务合约地址时调用,调用者必须是当前地址的拥有者;
业务代理调用接口:
calcAdd、const_calcAdd、makeSum,通过调用真实的业务合约接口,完成业务操作,获取执行结果,并通过事件机制返回业务执行结果;
说明:在本文案例中,代理合约中的业务代理接口,需要与业务合约中的相关接口一致,实际上,可以开发更加通用的方式,例如将业务合约接口的代理调用封装到一个可变参数的接口中完成。
业务合约1(calcContract)
业务合约1完成实际的业务操作,同时也需要实现一定的代理管理操作功能。从业务合约1的代码可以看到 ,作为被代理合约,在实现缺少必要的保护机制。合约代码如下:
#include <platon/platon.hpp>
#include
#includeCONTRACT calc_contract : public platon::Contract{
public:
ACTION void init(){
//the owner of the contract is best to be the operator of the deployment
//in this instance, owner address can not be changed
_ownerAddr.self() = std::pair<platon::Address, bool>(platon::platon_caller(), true);
}//methods for proxy mechanism
//this methods shall be called only after the proxy contract is deployed
ACTION bool RegisterProxy(const std::string& proxyAddr){
//set and register the proxy address
auto p_Addr = platon::make_address(proxyAddr);
if (!p_Addr.second){
_proxyAddr.self() = std::pair<platon::Address, bool>(platon::Address(), false);
platon::internal::platon_throw(“register proxy failed! illegal proxy address!”);
return false;
}
else{
_proxyAddr.self() = p_Addr;
return true;
}
}CONST std::string GetProxyAddress(){ return _proxyAddr.self().first.toString(); } CONST std::string GetOwnerAddress(){ return _ownerAddr.self().first.toString(); }
//the param is the next contract address the proxy really use
ACTION bool updateContract(const std::string& contractAddr){
//only owner can updateContract
auto send_Addr = platon::platon_caller();
if (_ownerAddr.self().first != send_Addr){
return false;
}//check the contract address auto c_Addr = platon::make_address(contractAddr); if (!c_Addr.second){ return false; } //call proxy if (!_proxyAddr.self().second){ return false; } auto result = platon::platon_call_with_return_value<bool>(_proxyAddr.self().first, (unsigned int)(0), (unsigned int)(0), "RegisterOwner", contractAddr); return result.second;
}
//calculation methods
ACTION int calcAdd(int a, int b){
return a + b;
}CONST int const_calcAdd(int a, int b){ return a + b; }
ACTION int makeSum(std::vector& eles){
int rst = 0;
for (auto itr = eles.begin(); itr != eles.end(); ++itr){
rst += *itr;
}
return rst;
}private:
//contracts using proxy mechanism are needed to using owner address principle.
platon::StorageType<“owner”_n, std::pair<platon::Address, bool>> _ownerAddr;platon::StorageType<“proxy”_n, std::pair<platon::Address, bool>> _proxyAddr;
};PLATON_DISPATCH(calc_contract, (init)(RegisterProxy)(GetProxyAddress)(GetOwnerAddress)(updateContract)(calcAdd)(const_calcAdd)(makeSum))
接口说明:
init:初始化owner,owner地址即合约的部署者;
代理管理接口:
RegisterProxy:注册代理合约地址,在业务合约1中没有对调用者进行限制,这会带来安全隐患;
updateContract:向代理合约申请地址变更请求,将业务合约转移至其他的业务合约,这是合约在线更新的一个重要机制。接口执行成功后,本合约不再被代理,在业务合约1中,没有对本地保存的代理地址进行清理,这也会带来一定的安全隐患;
业务功能接口:
calcAdd、const_calcAdd、makeSum,执行实际的业务操作。在业务合约1中,没有对调用者进行限制,这也会带来安全隐患。
业务合约2(calcContract)
业务合约2完成实际的业务操作,同时也需要实现一定的代理管理操作功能。业务合约2采用了较为规范的实现方式,其业务接口只能通过代理进行访问。合约代码如下:
#include <platon/platon.hpp>
#include <string>
#include <vector>
CONTRACT calc_contract2 : public platon::Contract
{
public:
ACTION void init(){
//the owner of the contract is best to be the operator of the deployment
//in this instance, owner address can not be changed
_ownerAddr.self() = std::pair<platon::Address, bool>(platon::platon_caller(), true);
//init the proxy
_proxyAddr.self() = std::pair<platon::Address, bool>(platon::Address(), false);
}
//this methods shall be called only after the proxy contract is deployed
ACTION bool RegisterProxy(const std::string& proxyAddr){
//only owner can Register Proxy
auto send_Addr = platon::platon_caller();
if (_ownerAddr.self().first != send_Addr){
return false;
}
//set and register the proxy address
auto p_Addr = platon::make_address(proxyAddr);
if (!p_Addr.second){
_proxyAddr.self() = std::pair<platon::Address, bool>(platon::Address(), false);
platon::internal::platon_throw("register proxy failed! illegal proxy address!");
return false;
}
else{
_proxyAddr.self() = p_Addr;
return true;
}
}
CONST std::string GetProxyAddress(){
if (_proxyAddr.self().second){
return _proxyAddr.self().first.toString();
}
else {
return "proxy not initialized!";
}
}
CONST std::string GetOwnerAddress(){
return _ownerAddr.self().first.toString();
}
//methods for proxy mechanism
//the param is the next contract address the proxy really use
ACTION bool updateContract(const std::string& contractAddr){
//only owner can updateContract
auto send_Addr = platon::platon_caller();
if (_ownerAddr.self().first != send_Addr){
return false;
}
//check the contract address
auto c_Addr = platon::make_address(contractAddr);
if (!c_Addr.second){
return false;
}
//call proxy
if (!_proxyAddr.self().second){
return false;
}
auto result = platon::platon_call_with_return_value<bool>(_proxyAddr.self().first, (unsigned int)(0), (unsigned int)(0), "RegisterContract", contractAddr);
if (!result.second){
return false;
}
//clear the proxy address
_proxyAddr.self() = std::pair<platon::Address, bool>(platon::Address(), false);
return result.second;
}
//calculation methods, the interface must be the same with the proxy
ACTION int calcAdd(int a, int b){
if (!_proxyAddr.self().second){
return -999999;
}
//only proxy could call
auto send_Addr = platon::platon_caller();
if (_proxyAddr.self().first != send_Addr){
return -999999;
}
//be different with contract1
return a + b + 1000000;
}
CONST int const_calcAdd(int a, int b){
if (!_proxyAddr.self().second){
return -999999;
}
//only proxy could call
auto send_Addr = platon::platon_caller();
if (_proxyAddr.self().first != send_Addr){
return -999999;
}
return a + b + 1000000;
}
ACTION int makeSum(std::vector<int>& eles){
if (!_proxyAddr.self().second){
return -999999;
}
//only proxy could call
auto send_Addr = platon::platon_caller();
if (_proxyAddr.self().first != send_Addr){
return -999999;
}
int rst = 0;
for (auto itr = eles.begin(); itr != eles.end(); ++itr){
rst += *itr;
}
//be different with contract1
return rst + 1000000;
}
private:
//contracts using proxy mechanism are needed to using owner address principle.
platon::StorageType<"owner"_n, std::pair<platon::Address, bool>> _ownerAddr;
platon::StorageType<"proxy"_n, std::pair<platon::Address, bool>> _proxyAddr;
};
PLATON_DISPATCH(calc_contract2, (init)(RegisterProxy)(GetProxyAddress)(GetOwnerAddress)(updateContract)(calcAdd)(const_calcAdd)(makeSum))
接口说明:
init:初始化owner,owner地址即合约的部署者;
代理管理接口:
RegisterProxy:注册代理合约地址,在业务合约2中对调用者进行了限制,只有合约owner(部署者)有权限调用,这是一种应对业务合约1中安全隐患的方式;
updateContract:在业务合约2中,变更申请执行成功后,会对本地管理的代理进行清理,这是一种应对业务合约1中安全隐患的方式;
业务功能接口:
calcAdd、const_calcAdd、makeSum,执行实际的业务操作。在业务合约2中,对调用者身份进行了限制,只有代理合约能够调用这些业务功能接口,即该合约的调用只能通过代理合约完成,这是一种应对业务合约1中安全隐患的方式,也是支持合约在线升级更新的一个重要机制;
业务合约2对每个计算结果,加了1000000,这是为了与合约1进行区分。
合约代理机制的运行机制
合约部署
从代理合约、业务合约的初始化接口可以看出,合约的部署有一定的推荐顺序(非强制)。由于代理合约的部署需要输入一个合约地址(一般是被代理的业务合约的地址,也可以是普通地址),因此代理合约一般在业务合约完成部署后,才进行部署(以业务合约地址作为参数)。
如果代理合约部署时使用了普通的地址,则后续代理合约实际进行业务代理时,需要通过部署时传入的普通地址,调用代理合约的RegisterContract,来注册更新业务合约的地址,此后更新地址需要通过注册成功的合约来调用完成。
合约部署的操作方法在PlatON官网、前面的系列文章中已经有了详细的阐述,详见官方wasm合约开发手册:https://devdocs.platon.network/docs/zh-CN/Wasm_Dev_Manual/,以及《 PlatON上的WASM智能合约开发(1)——合约开发入门》。
访问示例
在本文示例中,首先使用platon-truffle工具部署了业务合约1,然后利用该合约地址部署代理合约。
代理机制的访问调用基于client-sdk-python开发,在测试使用中如果遇到问题,请通过下方二维码联系cross技术团队。
访问示例的完整代码如下:
from client_sdk_python import Web3, HTTPProvider, WebsocketProvider
from client_sdk_python.eth import PlatON
true = True
false = False
from_address = '...'
proxyAddr = '...'
proxy_abi = []
contractAddr = '...'
contract_abi = []
contractAddr_2 = '...'
contract_2_abi = []
def proxyCall():
w3 = Web3(HTTPProvider("http://127.0.0.1:6789"))
platon = PlatON(w3)
hello = platon.wasmcontract(address=proxyAddr, abi=proxy_abi,vmtype=1)
tx_events_hash = hello.functions.calcAdd(73, 8).transact({'from':from_address,'gas':1500000})
tx_events_receipt = platon.waitForTransactionReceipt(tx_events_hash)
rstAdd2 = hello.events.incalcAdd().processReceipt(tx_events_receipt)
print('***********************calcAdd: ')
print(rstAdd2)
tx_events_hash_sum = hello.functions.makeSum([11, 12, 13]).transact({'from':from_address,'gas':1500000})
tx_events_receipt_sum = platon.waitForTransactionReceipt(tx_events_hash_sum)
rstAdd_sum = hello.events.inmakeSum().processReceipt(tx_events_receipt_sum)
print('')
print('***********************makeSum: ')
print(rstAdd_sum[0]['args']['arg1'])
return
def contract_1_Call():
w3 = Web3(HTTPProvider("http://127.0.0.1:6789"))
platon = PlatON(w3)
hello = platon.wasmcontract(address=contractAddr, abi=contract_abi,vmtype=1)
rstAdd = hello.functions.calcAdd(73, 8).call()
print(rstAdd)
rstConst_Add = hello.functions.const_calcAdd(100, 99).call()
print(rstConst_Add)
rstSum = hello.functions.makeSum([11, 12, 13]).call()
print(rstSum)
return
def contract_2_Call():
w3 = Web3(HTTPProvider("http://127.0.0.1:6789"))
platon = PlatON(w3)
hello = platon.wasmcontract(address=contractAddr_2, abi=contract_2_abi,vmtype=1)
rstAdd = hello.functions.calcAdd(73, 8).call()
print(rstAdd)
rstConst_Add = hello.functions.const_calcAdd(100, 99).call()
print(rstConst_Add)
rstSum = hello.functions.makeSum([11, 12, 13]).call()
print(rstSum)
return
def contract_1_to_2():
w3 = Web3(HTTPProvider("http://127.0.0.1:6789"))
platon = PlatON(w3)
hello = platon.wasmcontract(address=contractAddr, abi=contract_abi,vmtype=1)
print(hello.functions.GetOwnerAddress().call())
print(hello.functions.GetProxyAddress().call())
tx_events_hash = hello.functions.updateContract(contractAddr_2).transact({'from':from_address,'gas':1500000})
tx_events_receipt = platon.waitForTransactionReceipt(tx_events_hash)
print(tx_events_receipt)
def c2_registerProxy():
w3 = Web3(HTTPProvider("http://127.0.0.1:6789"))
platon = PlatON(w3)
hello = platon.wasmcontract(address=contractAddr_2, abi=contract_2_abi,vmtype=1)
print(hello.functions.GetProxyAddress().call())
tx_events_hash = hello.functions.RegisterProxy(proxyAddr).transact({'from':from_address,'gas':1500000})
tx_events_receipt = platon.waitForTransactionReceipt(tx_events_hash)
print(tx_events_receipt)
print(hello.functions.GetProxyAddress().call())
def contract_2_to_1():
w3 = Web3(HTTPProvider("http://127.0.0.1:6789"))
platon = PlatON(w3)
hello = platon.wasmcontract(address=contractAddr_2, abi=contract_2_abi,vmtype=1)
print(hello.functions.GetOwnerAddress().call())
print(hello.functions.GetProxyAddress().call())
tx_events_hash = hello.functions.updateContract(contractAddr).transact({'from':from_address,'gas':1500000})
tx_events_receipt = platon.waitForTransactionReceipt(tx_events_hash)
print(tx_events_receipt)
def whichContract():
w3 = Web3(HTTPProvider("http://127.0.0.1:6789"))
platon = PlatON(w3)
hello = platon.wasmcontract(address=proxyAddr, abi=proxy_abi,vmtype=1)
rst = hello.functions.const_calcAdd(73, 8).call()
print(rst)
业务合约1
业务合约1直接调用
代码:
contract_1_Call()
输出:
说明:
业务合约1中未作代理注册的限制,因此能直接执行操作。
代理调用业务合约1操作
代码:
proxyCall()
输出:
说明:
通过事件机制获取操作结果;
操作结果可在捕捉到的返回事件中,通过“rstAdd_sum[0][‘args’][‘arg1’]”来获取。
部署业务合约2
使用platon-truffle工具部署业务合约2,获取其地址。
业务合约1,调用代理变更操作
代码:
contract_1_to_2()
输出:
业务合约2
代理调用业务合约2操作
此时,业务合约2尚未调用RegisterProxy进行代理注册。
代码:
proxyCall()
输出:
说明:
业务合约2中做了代理限制,在尚未注册代理时,无法完成正常的业务操作;
代理调用业务合约2虽然成功了,但是结果并非期望的值,这是一种在线更新的安全机制。
业务合约2注册代理
代码:
c2_registerProxy()
输出:
说明:
注册前后分别调用GetProxyAddress,观察当前管理的代理信息。
代理调用业务合约2操作
代码:
proxyCall()
输出:
说明:
完成代理注册后,业务合约调用成功,返回期望结果。
业务合约2,调用代理变更操作
代码:
contract_2_to_1()
输出:
说明:
连续调用两次,第二次会发现业务合约2已经执行了代理信息清理工作。