为什么我不能在NEAR合同中阅读此HashMap?

问题描述

我有一个NEAR智能合约,该合约保留Veggie记录的HashMap。我最基本的访问器方法get_veggie(vid)查找并返回一条Veggie记录,通过了单元测试,但未部署合同。当我发送另一个访问器方法get_veggie_keys()返回的键之一时,它会因“蔬菜不存在”而感到恐慌。

// this method returns an array of u64 keys:
pub fn get_veggie_keys(&self) -> Vec<TokenId> {
    self.veggies.keys().cloned().collect()
}

// but this method panics when I give it one of those keys:
pub fn get_veggie(&self,vid: TokenId) -> Veggie {
    let veggie = match self.veggies.get(&vid) {
        Some(c) => {
            c
        },None => {
            env::panic(b"Veggie does not exist.")
        }
    };
    veggie.clone()
}

当我从NEAR CLI调用这些方法时,我看到了这种现象:

% near call --accountId $ACCOUNTID $CONTRACT_NAME get_veggie_keys      
Scheduling a call: dev-1602786511653-5521463.get_veggie_keys()
Transaction Id FdWjevTsMD73eFPno41THrvrChfB9HDoLAozuiXsBwru
To see the transaction in the transaction explorer,please open this url in your browser
https://explorer.testnet.near.org/transactions/FdWjevTsMD73eFPno41THrvrChfB9HDoLAozuiXsBwru
[ 3469591985938534000,[length]: 1 ]
%
% near call --accountId $ACCOUNTID $CONTRACT_NAME get_veggie '{"vid":3469591985938534000}'
Scheduling a call: dev-1602786511653-5521463.get_veggie({"vid":3469591985938534000})
Receipt: 68ahRQyNN7tzAQMbguCEy83ofL6S5mv3iLVmmN2NH8gh
    Failure [dev-1602786511653-5521463]: Error: Smart contract panicked: Veggie does not exist.
An error occured [...]

合同中的这种行为与单元测试中的行为有何不同?我称方法错误吗?我不了解HashMaps吗?感谢您的任何建议。也许我犯了一个Rust noob错误,但我对此深感困惑...

解决方法

您正在靠近那里。您将要使用U64U128这是特殊的JSON类型。另外,您也不想使用HashMap,因为它无法缩放。 我在这里创建了一个示例存储库供您查看: https://github.com/mikedotexe/near-stackoverflow-64378144/blob/master/src/lib.rs

如果参数要使用需要超过53位的数字,则需要这样做:

use near_sdk::json_types::U128;
…
pub fn add_veggie_taste(&mut self,upc: U128,taste: String) {
  …
}

如上所述,您将要使用NEAR集合而不是HashMap。请查看此链接以了解各种选项: https://docs.rs/near-sdk/2.0.0/near_sdk/collections/index.html

此外,我相信您可能没有像下面那样在结构上方添加宏。对于单元测试而不是链上测试,这将是有意义的。

#[near_bindgen]
#[derive(BorshDeserialize,BorshSerialize)]
pub struct Produce {
    pub veggies_taste: TreeMap<UPC,String>,pub veggies_taste2: TreeMap<UPC,String>
}

#[near_bindgen]
impl Produce {
  …
  pub fn add_veggie_taste(&mut self,taste: String) {
    let existing_veggie: Option<String> = self.veggies_taste.get(&upc.into());
    if existing_veggie.is_some() {
      env::panic(b"Sorry,already added this UPC!")
    }
    // Note that "into" turns the U128 (as a string,basically) into u128
    self.veggies_taste.insert(&upc.into(),&taste);
  }
  …
} 

我受到这篇文章的启发,并决定在此处制作视频: https://youtu.be/d68_hw_zjT4

,

警告:Javascript silently truncates integers of more than 53 bits! Rust能够以某种方式将u64变量编译为WASM,并且它们可以在合同中运行,但是Near-Bindgen添加的公共JSON-RPC方法仍将其值截断为53位。因此,即使我的Rust代码包含一个偷偷摸摸的数据类型转换错误,其编译过程也不会发出警告。

我创建了Veggie记录的影子版本VeggieJSON,然后添加了满足From特性的转换方法,并在公共合同调用中添加了包装方法以在Veggie和VeggieJSON之间来回转换。现在可以使用。

pub struct Veggie {
    pub vid: TokenId,pub vtype: VeggieType,pub vsubtype: VeggieSubType,pub parent: TokenId,pub dna: u64,pub meta_url: String,}

pub type TokenJSON = String;

#[derive(PartialEq,Clone,Debug,Serialize,BorshDeserialize,BorshSerialize)]
pub struct VeggieJSON {
    pub vid: TokenJSON,pub parent: TokenJSON,pub dna: String,}

impl From<Veggie> for VeggieJSON {
    fn from(v: Veggie) -> Self {
        Self {
            vid: v.vid.to_string(),vtype: v.vtype,vsubtype: v.vsubtype,parent: v.parent.to_string(),dna: v.dna.to_string(),meta_url: v.meta_url
        }
    }
}

impl From<VeggieJSON> for Veggie {
    fn from(v: VeggieJSON) -> Self {
        Self {
            vid: v.vid.parse::<TokenId>().unwrap(),parent: v.parent.parse::<TokenId>().unwrap(),dna: v.dna.parse::<u64>().unwrap(),meta_url: v.meta_url,}
    }
}

fn get_veggie_json(&self,vid: TokenJSON) -> VeggieJSON {
    self.get_veggie(vid.parse::<TokenId>().unwrap()).into()
}

[ ... ]

这有效,但是有点官僚主义。是否存在更简洁,惯用的解决方案,用于从/向网络转换记录的数据表示形式?