Why can't I read this HashMap in a NEAR contract?

∥☆過路亽.° 提交于 2021-01-28 07:50:34

问题


I have a NEAR smart contract that keeps a HashMap of Veggie records. My most basic accessor method get_veggie(vid), which looks up and returns one Veggie record, passes unit tests but fails in the deployed contract. It panics with 'veggie does not exist' when I send one of the keys returned by another accessor method, 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()
}

I see this behavior when I call these methods from the 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 [...]

How is this behavior different in the contract than in unit tests? Am I calling the method wrong? Do I not understand HashMaps? Thanks for any advice. Maybe I'm making a Rust noob error, but I'm deeply puzzled here ...


回答1:


You're getting close there. You'll want to use U64 or U128 which are special JSON types. Also, you'll not want to use a HashMap as that doesn't scale. I have created an example repository here for you to see: https://github.com/mikedotexe/near-stackoverflow-64378144/blob/master/src/lib.rs

If a parameter is going to take a number that requires more than 53 bits you'll need to do it like this:

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

As mentioned, you'll want to use a NEAR Collection instead of HashMap. Please see this link for the various options: https://docs.rs/near-sdk/2.0.0/near_sdk/collections/index.html

Also, I believe you may not have added the macros above your struct as we have below. This would make sense for it working with unit tests but not on-chain.

#[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, upc: U128, 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);
  }
  …
} 

I was inspired by this post and decided to make a video here, as well: https://youtu.be/d68_hw_zjT4




回答2:


Caution: Javascript silently truncates integers of more than 53 bits! Somehow Rust is able to compile u64 variables into WASM and they work in the contract, but the public JSON-RPC methods added by Near-Bindgen still truncate their values to 53 bits. So my Rust code compiled without warnings, even though it contained a sneaky data type conversion bug.

I created a shadow version of the Veggie record, VeggieJSON, then added conversion methods to satisfy the From trait, and wrapper methods on the public contract calls to convert back and forth between Veggie and VeggieJSON. This works now.

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 vtype: VeggieType,
    pub vsubtype: VeggieSubType,
    pub parent: TokenJSON,
    pub dna: String,
    pub meta_url: 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(),
            vtype: v.vtype,
            vsubtype: v.vsubtype,
            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()
}

[ ... ]

This works, but is a little bureaucratic. Is there a more concise, idiomatic solution for translating a record's data representation from/to the network?



来源:https://stackoverflow.com/questions/64378144/why-cant-i-read-this-hashmap-in-a-near-contract

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!