想用Wasm開發dApp?你不得不讀的入門教程(3)

買賣虛擬貨幣
在上期的技術視點中,我們簡單介紹瞭如何在不依賴模板的情況下,完成一個簡單的 Ontology Wasm 合約的開發,並介紹了 Ontology Wasm 工具庫 API 文件的生成方式,方便開發者查詢和呼叫已提供的功能。但我們發現在合約開發的過程中,通常需要進行以下操作:· 解析呼叫合約的引數;· 將自定義的結構體儲存到鏈上;· 從鏈上讀取已存在的資料並解析成原始的資料型別;· 跨合約呼叫的時候,傳遞目標合約需要的引數。上面所列出來的情況,均涉及到引數的序列化和反序列化問題,本文將會詳細介紹 Ontology Wasm 合約中引數序列化和反序列化的方法。
Encoder和Decoder介面Encoder 介面定義了不同型別資料的序列化方法,具體實現邏輯請看sink.rs檔案;Decoder 介面定義了不同型別資料的反序列化方法,具體實現邏輯請看source.rs檔案。ontology-wasm-cdt-rust 工具庫中已經為常用的資料型別實現 Encoder 和 Decoder 介面,有 u8、u16、u32、u64、u128、bool、Address、H256、Vec、&str、String 和 Tuple 多種型別。常用資料型別的序列化和反序列化示例如下:let mut sink = Sink::new(16);sink.write(1u128);sink.write(true);let addr = Address::repeat_byte(1);
sink.write(addr);sink.write("test");let vec_data = vec!["hello","world"];sink.write(&vec_data);let tuple_data = (1u8, "hello");sink.write(tuple_data);
let mut source = Source::new(sink.bytes());let res1:u128 = source.read().unwrap_or_default();let res2:bool = source.read().unwrap_or_default();let res3:Address = source.read().unwrap_or_default();let res4:&str = source.read().unwrap_or_default();let res5:Vec<&str> = source.read().unwrap_or_default();
let res6:(u8, &str) = source.read().unwrap_or_default();assert_eq!(res1, 1u128);assert_eq!(res2, true);assert_eq!(res3, addr);assert_eq!(res4, "test");assert_eq!(res5, vec_data);
assert_eq!(res6, tuple_data);sink.write()方法傳入的引數支援所有已經實現 Encoder 介面的資料型別,序列化的時候需要宣告其資料型別,比如1u128等。source.read()方法可以讀取所有已經實現 Decoder 介面的資料型別,反序列化的時候要指明其結果的資料型別,如下面的程式碼:let res1:u8 = source.read().unwrap_or_default();在 res1後面要宣告其資料型別是 u8。也可以寫成:let res1 = source.read_byte().unwrap_or_default();
在讀取的時候,已經使用了read_byte,所以不需要在 res1後面宣告資料型別了。解析呼叫合約的引數合約在獲得呼叫引數時透過runtime::input()方法獲得,但是該方法僅能拿到bytearray格式的引數,需要反序列化成對應的引數,第一個引數是合約方法名,後面的是方法引數,示例如下:let input = runtime::input();let mut source = Source::new(&input);let method_name: &str = source.read().unwrap();
let mut sink = Sink::new();match method_name {    "transfer" => {        let (from, to, amount) = source.read().unwrap();        sink.write(ont::transfer(from, to, amount));    }
    _ => panic!("unsupported action!"),}Rust 支援型別推導,大部分情況下可以省略型別宣告,比如在ont::transfer()方法中已經宣告瞭 from、to 和 amount 的資料型別,所以在前面解析 from、to 和 amount 的時候沒有宣告資料型別。如果引數是 Vec<&str>型別,可以按照如下的方式進行反序列化:let param:Vec<&str> = source.read().unwrap();如果引數是 Vec<(&str,U128,Address)>型別,可以按照如下的方式進行反序列化:
let param:Vec<(&str,U128,Address)>= source.read().unwrap();序列化自定義結構體在合約開發中,我們經常需要對struct型別的資料進行序列化和反序列化,為了達到這個目的,我們僅需要在struct宣告的地方加上#[derive(Encoder, Decoder)]即可,示例如下:#[derive(Encoder, Decoder)]struct ReceiveRecord {    account: Address,
    amount: u64,}#[derive(Encoder, Decoder)]struct EnvlopeStruct {    token_addr: Address,    total_amount: u64,
    total_package_count: u64,    remain_amount: u64,    remain_package_count: u64,    records: Vec<ReceiveRecord>,}在使用該功能的時候,要注意struct的每個欄位必須都已經實現Encoder和Decoder介面,加上該註解後,就可以按照如下的方式序列化和反序列化了:
let addr = Address::repeat_byte(1);let rr = ReceiveRecord{    account: addr,    amount: 1u64,};let es = EnvlopeStruct{
    token_addr: addr,    total_amount: 1u64,    total_package_count: 1u64,    remain_amount: 1u64,    remain_package_count: 1u64,    records: vec![rr],
};let mut sink = Sink::new(16);sink.write(&es);let mut source = Source::new(sink.bytes());let es2:EnvlopeStruct = source.read().unwrap();assert_eq!(&es.token_addr,&es2.token_addr);
assert_eq!(&es.total_amount,&es2.total_amount);assert_eq!(&es.total_package_count,&es2.total_package_count);assert_eq!(&es.remain_amount,&es2.remain_amount);assert_eq!(&es.remain_package_count,&es2.remain_package_count);從鏈上讀取指定型別的資料合約中不同型別的資料在儲存到鏈上之前,需要先序列化成bytearray型別的資料,從鏈上讀取資料時,讀到的也都是bytearray型別的資料,需要反序列化成指定的資料型別。database模組提供了更加簡便的介面供開發者使用。
· fn put<K: AsRef<[u8]>, T: Encoder>(key: K, val: T) 根據 key 儲存 T 型別的資料,要求 T 型別實現Encoder介面示例:let es = EnvlopeStruct{    token_addr: addr,    total_amount: 1u64,    total_package_count: 1u64,    remain_amount: 1u64,
    remain_package_count: 1u64,    records: vec![rr],};database::put("key", es);我們從database::put的原始碼可以看到,該方法在執行的時候,會先序列化 es 引數,然後將序列化結果儲存到鏈上。· fn get<K: AsRef<[u8]>, T>(key: K) -> Option<T> where for<'a> T: Decoder<'a> + 'static,根據 key 獲得指定型別 T 的資料,其中,T 型別要求實現了 Decoder 介面。示例:
let res:EnvlopeStruct = database::get("key").unwrap();我們從database::get的原始碼可以看到,該方法在執行的時候,會先從鏈上拿到 bytearray 型別的資料,然後再反序列化得到 EnvlopeStruct 型別的資料。跨合約引數傳遞在跨合約呼叫的時候,引數是以 bytearray 的格式進行傳遞的,所以需要將不同型別的資料進行序列化,下面以跨合約呼叫 Wasm 合約為例。let (contract_address,method,a, b): (&Address,&str,u128, u128) = source.read().unwrap();let mut sink = Sink::new(16);
sink.write(method);sink.write(a);sink.write(b);let resv = runtime::call_contract(contract_address, sink.bytes()).expect("get no return");該段程式碼實現了一個合約呼叫另一個 Wasm 合約的功能,呼叫的合約先解析出來引數,然後根據解析的引數去呼叫另外一個合約時,需要將方法名和方法引數依次序列化,透過 sink.bytes()方法拿到序列化的結果,最後透過runtime模組中的 call_contract方法實現跨合約呼叫的功能。結語
本文詳細介紹了 Ontology Wasm 合約中引數序列化和反序列化的方法。主要包括在合約執行的過程中如何解析外部傳進來的引數,在合約中自定義的結構體如何進行序列化和反序列化,鏈上讀取的 bytearray 型別的資料如何反序列化成原始的資料型別,以及原始的資料型別如何序列化成 bytearray 型別的資料儲存到鏈上,最後介紹了跨合約呼叫中如何進行引數的傳遞。Wasm 技術具有十分龐大活躍的社羣,且 Wasm 可以支援更加複雜的合約,並擁有豐富的第三庫支援,生態十分完善,開發門檻比較低,在 Ontology 鏈上開發和部署 Wasm 合約對於想要入門的開發者來說十分友好。歡迎全球各地的開發者加入我們的技術社羣,共建繁榮的 Ontology 生態。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;