写合约的时候,我们需要获取链上的数据,但是在你编译合约的时候,合约没有这个能力,所以,我们把它做成一个接口,等到真正在链上执行的时候,wasm就可以帮你把它链接到对应的数据上,wasm的这部分,我们下次介绍wasm虚拟机部分的时候,在介绍。今天就说一下,如何实现合约语言中,这部分的实现。
我们以获取区块高度,来详细介绍一些流程。
#[call(contract = "xq", function = "abc")]
fn rcv<C: Context + Copy>(ctx: C) -> CResult<RetValue> {
let a: Param = ctx.paramteter();
let ret = RetValue {
name: a.name,
age: a.age,
sex: a.sex,
};
let num = ctx.block_number();
//let a:Param = ctx.paramteter();
Ok(ret)
}
ctx.block_number()就是获取当前区块高度的函数。但是这个ctx是个泛型,他是一个context trait;这个context是怎么来的,可以看看你上一节,如何生成init函数中,介绍的;
他的定义如下,
pub trait Context{
fn owner(&self) -> Address;
fn invoker(&self) -> Address;
fn self_address(&self) -> Address;
fn self_balance(&self) -> u64;
fn paramteter<T:>(&self)-> T where T: serde::de::DeserializeOwned;
fn store_get<T>(&self, key: String)->T where T: serde::de::DeserializeOwned;
fn store_set(&self, key: String, value:String)->bool;
fn error(&self, err:String);
fn event(&self, event:String);
fn return_data(&self, data:String);
fn transfer(&self, addr:Address, amount:u64)->bool;
fn call(&self, addr:Address, amount:u64, func:String, rg:String)->bool;
fn block_time(&self)->u64;
fn block_number(&self)->u64;
fn tx_hash(&self)->String;
}
我们可以看到它里边的函数都是一些,和链相关的,例如获取地址,余额,高度时间等;我们写合约的时候,就是通过它,实现和链交互的,他是内置在合约语言里的;
实际上他是由contractcontex实现的,它的定义是空,因为我们需要的数据,都在链上,所以这里定义一个空的;
#[derive(Clone, Copy)]
pub struct ContractContext;
下面我们来看看,他是怎么实现Context trait的;
impl Context for ContractContext {
fn owner(&self) -> Address {
let mut bytes: MaybeUninit<[u8; ADDRESS_NUM_LEN]> = MaybeUninit::uninit();
let ptr = bytes.as_mut_ptr();
let address = unsafe {
get_owner(ptr as *mut u8);
bytes.assume_init()
};
Address(address)
}
fn invoker(&self) -> Address {
let mut bytes: MaybeUninit<[u8; ADDRESS_NUM_LEN]> = MaybeUninit::uninit();
let ptr = bytes.as_mut_ptr();
let address = unsafe {
get_invoker(ptr as *mut u8);
bytes.assume_init()
};
Address(address)
}
… …
}
我们看owner函数,他是获取当前合约拥有者的地址,可以看到,它先分配了一个地址长度的数组MaybeUninit<[u8; ADDRESS_NUM_LEN]>,准备用来放地址;然后是一个unsafe的,在里边调用了一个get_owner()函数,返回address;组装Address返回给用户;至于其中的MaybeUninit,是为了让变量bytes,在get_owner获取到address之后才进行初始化;
我们再来看一下get_owenr的定义,如下
#[cfg_attr(target_arch = "wasm32", link(wasm_import_module = "xq"))]
extern "C" {
pub(crate) fn get_invoker(value: *mut u8);
pub(crate) fn get_owner(value: *mut u8);
pub(crate) fn get_contract_address(value: *mut u8);
pub(crate) fn get_contract_balance() -> u64;
pub(crate) fn get_parameter(value: *mut u8) -> u32;
… …
pub(crate) fn get_block_time() -> u64;
pub(crate) fn get_block_hash(value: *mut u8) -> u64;
pub(crate) fn get_block_height() -> u64;
pub(crate) fn set_event(start: *const u8, length: u32) -> i32;
pub(crate) fn set_return_data(start: *const u8, length: u32) -> u32;
}
Target_arch = "32"告诉编译器,编译的成wasm32平台的格式,wasm_import_module则是定义wasm的input模块的名字,后边有机会,可以用rust实现版wasm,详细说一下,wasm的构造;其实当前项目中,原计划是自己写一个简单的,但是写了一半,没写完,所以后边直接用wasmtime了。
get_owenr是FFI,通过extern “C”定义了一组外部接口,其实是和context trait对应的;这意味着,wasm会提供这样一组实现相应功能的接口,这样合约里调用链上的数据的整个链条就完成一半了,另外一半,在wasm虚拟机中实现。这些函数都是使用基础类型,指针,u32之类的,这是因为FFI要兼容C的ABI。