How should I handle smart contract structure change in NEAR protocol? - rust

Suppose I have a contract
...
pub struct Contract {
collection_a: Vector<String>,
}
After I deploy this version, when I change the data structure of my smart contract, for e.g.
pub struct Contract {
collection_a: Vector<String>,
collection_b: Vector<String>,
}
I ran into an error when interacting with the contract
Failure [dev-1644158197214-15380220543819]: Error: {"index":0,"kind":{"ExecutionError":"Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: \"Unexpected length of input\" }', /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/near-sdk-3.1.0/src/environment/env.rs:786:46"}}
ServerTransactionError: {"index":0,"kind":{"ExecutionError":"Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: \"Unexpected length of input\" }', /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/near-sdk-3.1.0/src/environment/env.rs:786:46"}}
at Object.parseResultError (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/node_modules/near-api-js/lib/utils/rpc_errors.js:31:29)
at Account.signAndSendTransactionV2 (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/node_modules/near-api-js/lib/account.js:160:36)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async scheduleFunctionCall (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/commands/call.js:57:38)
at async Object.handler (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/utils/exit-on-error.js:52:9) {
type: 'FunctionCallError',
context: undefined,
index: 0,
kind: {
ExecutionError: `Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: "Unexpected length of input" }', /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/near-sdk-3.1.0/src/environment/env.rs:786:46`
},
transaction_outcome: {
proof: [ [Object], [Object] ],
block_hash: '5mPRmggsyL9cNsgS4a6mzRT7ua9Y8SS8XJbW9psawdDr',
id: '8BeARer3UXLoZ3Vr22QAqkyzsp143D7FCtVssjyYxzs',
outcome: {
logs: [],
receipt_ids: [Array],
gas_burnt: 2427936651538,
tokens_burnt: '242793665153800000000',
executor_id: 'dev-1644158197214-15380220543819',
status: [Object],
metadata: [Object]
}
}
}
How can I handle this situation when I need to update the structure?

What you want is storage migration:
#[derive(BorshSerialize, BorshDeserialize)]
pub struct OldContract {
collection_a: Vector<String>,
}
#[near_bindgen]
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Contract {
collection_a: Vector<String>,
collection_b: Vector<String>,
}
#[near_bindgen]
impl Contract {
#[private]
#[init(ignore_state)]
pub fn migrate() -> Self {
let old_storage: OldContract = env::state_read().expect("Couldn't read old state");
Self {
collection_a: old_storage.collection_a,
collection_b: Vec::new(),
}
}
}
First you update your contract with this code, then you call the migrate method with the contract key. On your next upgrade, you can delete the method and OldContract struct to save on storage.
A problem you might run into are storage migrations that do not fit into a single block. AFAIK, there is no solution for that. Borsh serialization however is deterministic, so as long as you keep your data structures as enums, you should be able to just reinterpret the current chain storage and split migrations into multiple partial migrations. Make sure to thoroughly test that, you run the risk of irrecoverably screwing up your contracts state.

Related

How to get my prost_types::DescriptorProto for my message from .proto file?

How to get my prost_types::DescriptorProto for my message from .proto file?
I have written a rust program to invoke google bigquery storage write API via grpc w/ protobuf by gcloud-sdk "google.cloud.bigquery.storage.v1".
https://github.com/abdolence/gcloud-sdk-rs
2023-02-17T08:16:04.492220Z DEBUG gcloud_sdk::middleware: OK: /google.cloud.bigquery.storage.v1.BigQueryWrite/AppendRows took 262ms (incl. token gen: 0ms) Error: Status { code: InvalidArgument, message: "Invalid proto schema: BqMessage.proto: my.proto: \"my.proto\" is not a valid identifier. Entity: projects/steam-sequencer-xxxxxx/datasets/my_dataset/tables/test_table/streams/_default", source: None }
There is an argument puzzling me particularly!
pub struct ProtoSchema { pub proto_descriptor: Option<DescriptorProto>, }
ProtoSchema describes the schema of the serialized protocol buffer data rows.
It's a "prost_types::DescriptorProto" indeed.
In many languages, it is generated/supported by the languages/package out-of-the-box.
Because I am a newbie for Rust, Prost & Grpc, does any one know how to get the DescriptorProto struct from my proto file via prost / tonic crate ?
Thank you very much!
let x = DescriptorProto {
name: Some(String::from("my.proto")),
field: vec![],
extension: vec![],
nested_type: vec![],
enum_type: vec![],
extension_range: vec![],
oneof_decl: vec![],
options: None,
reserved_range: vec![],
reserved_name: vec![],
};
let proto_schema = ProtoSchema {
proto_descriptor: Some(x),
};
let proto_data = ProtoData {
writer_schema: Some(proto_schema),
rows: Some(proto_rows),
};

How can I transfer a token from a contract to a wallet from within the contract

So I am trying to transfer a FT, saved as a file ft.rs, which is generated in the standard way, like so:
impl Contract {
/// Initializes the contract with the given total supply owned by the given `owner_id` with
/// default metadata (for example purposes only).
#[init]
pub fn new_default_meta(owner_id: AccountId, total_supply: U128) -> Self {
Self::new(
owner_id,
total_supply,
FungibleTokenMetadata {
spec: FT_METADATA_SPEC.to_string(),
name: "Example NEAR fungible token".to_string(),
symbol: "EXAMPLE".to_string(),
icon: None,
reference: None,
reference_hash: None,
decimals: 24,
},
)
}
/// Initializes the contract with the given total supply owned by the given `owner_id` with
/// the given fungible token metadata.
#[init]
pub fn new(
owner_id: AccountId,
total_supply: U128,
metadata: FungibleTokenMetadata,
) -> Self {
assert!(!env::state_exists(), "Already initialized");
metadata.assert_valid();
let mut this = Self {
token: FungibleToken::new(b"a".to_vec()),
metadata: LazyOption::new(b"m".to_vec(), Some(&metadata)),
};
this.token.internal_register_account(&owner_id);
this.token.internal_deposit(&owner_id, total_supply.into());
near_contract_standards::fungible_token::events::FtMint {
owner_id: &owner_id,
amount: &total_supply,
memo: Some("Initial tokens supply is minted"),
}
.emit();
this
}
}
And I want to make another contract which can do two things, send the ft, and receive an ft:
pub use crate::ft::*;
mod ft;
impl Contract {
#[payable]
fn receive_ft(&self) -> bool {
// user sends ft and this contract registers and stores it
}
#[payable]
fn send_ft(&self, receiver : AccountId, amount : U128) -> bool {
// user sends ft to external address
}
}
Anyone here to point me in the right direction?
your going to want to import the FungibleTokenReceiver from the near-contract-standards crate and then implement it in it's own impl block in something what would look like this:
use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver;
use near_sdk::{near_bindgen, PromiseOrValue};
use near_sdk::json_types::{ValidAccountId, U128};
#[near_bindgen]
impl FungibleTokenReceiver for Contract {
pub fn ft_on_transfer(&mut self, sender_id: ValidAccountId, amount: U128, msg: String) -> PromiseOrValue<U128> {
// Your logic goes here make sure you return a promise that would
// be the value of the number of tokens you want to send back to
// the account [if any]
PromiseOrValue::Value(U128::from(0))
}
}
As for the sending out of tokens, I don't know the exact implementation, but you would have to implement a cross contract call on the fungible_token contract to transfer the tokens from the contract to the contract caller
Hope this helps :)

Trouble with reqwest: Unable to deserialize JSON response + `match` issues

I am learning Rust and it's web api support. I am working on a simple project which does a web API call. Here's the intuition:
(It's all about getting Sprint dates from an Azure DevOps project)
In src/getlatestsprint.rs I have a struct that has fields called "date" and "sprint". They're both String type.
pub struct Sprint {
date: String,
sprint: String,
}
I am then doing an impl on it and I am expecting it to return the Sprint itself, and it's supposed to have the date and the sprint number. FYI, what it is returning in the code is just placeholder.
The trouble I am having is with the response (not this block).
impl Sprint {
pub fn get_sprint_and_date() -> Result<Sprint, reqwest::Error> {
let sprint_url: &str = "https://dev.azure.com/redactedOrg/redactedProj/redactedTeam/_apis/work/teamsettings/iterations?api-version=6.0";
let pat: &str = "redactedPAT";
let client = reqwest::blocking::Client::new();
let get_request = client.get(sprint_url)
.basic_auth(&"None", Some(pat))
.send()?;
All this works. get_request.status() is 200 and get_request.text() is Ok(JSON_respose). The trouble starts from here:
if get_request.status() == 200 {
match get_request.text() {
Ok(v) => {
let deserialized_json: HashMap<String, serde_json::Value> = serde_json::from_str(&v).unwrap(); //This I got from another SO post that I was going through to work this out.
match deserialized_json.get("value") {
Some(des_j) => des_j,
None => None, //It says here "expected `&serde_json::Value`, found enum `std::option::Option`" This is where I am lost really.
}
},
_ => (),
}
}
//Placeholder return statement (I know I don't need to write return, but I am just doing it because I am used to it in other languages)
return Ok(Sprint {
date: String::from("sdfsd"),
sprint: String::from("dsfsfsdfsdfsd"),
})
}
}
My intention is to return the latest date and sprint got from the response as a Struct.
What am I doing wrong here?
EDIT
Sample JSON response:
{
"count": 82,
"value": [
{
"id": "redactedID",
"name": "Sprint 74",
"path": "redactedProjectName\\Sprint 74",
"attributes": {
"startDate": "2018-10-22T00:00:00Z",
"finishDate": "2018-11-04T00:00:00Z",
"timeFrame": "past"
},
"url": "dev.azure.com/redactedOrg/redactedProject/redactedProj/_apis/…"
}
]
}
So I managed to get what I wanted. I wasn't aware that to work with nested JSON, I had to create a Struct for each of the nested keys. Not just that, I had to even match the case of the field that I am getting the response of. Weird, but OK. I am pretty sure there's an easier way ti do this, but I was able to resolve it myself.
Here are the Structs that I created.
#[derive(Deserialize, Serialize)]
struct Response {
count: u32,
value: Vec<Value>,
}
#[derive(Deserialize, Serialize, Debug)]
struct Value {
id: String,
name: String,
path: String,
attributes: Attributes,
url: String,
}
#[derive(Deserialize, Serialize, Debug)]
struct Attributes {
startDate: String,
finishDate: String,
timeFrame: String,
}
We can serialize and deserialize directly to a custom struct using serde.
Let's start by deriving Serialize and Deserialize for Sprint:
//This requires the serde crate with derive feature
use serde::{Serialize, Deserialize};
#[derive(Deserialize, Serialize)]
pub struct Sprint {
date: String,
sprint: String,
}
Then we can clean up the implementation:
impl Sprint {
pub fn get_sprint_and_date() -> Result<Sprint, reqwest::Error> {
let sprint_url: &str = "https://dev.azure.com/redactedOrg/redactedProj/redactedTeam/_apis/work/teamsettings/iterations?api-version=6.0";
let pat: &str = "redactedPAT";
let client = reqwest::blocking::Client::new();
let get_request = client.get(sprint_url)
.basic_auth(&"None", Some(pat))
.send()?;
//This requires the reqwest crate with the json feature
let sprint: Sprint = get_request.json()?;
Ok(sprint)
}
}

Can one implement/derive Serialize on an enum without #[derive(Serialize)]?

I'm using rust + rocket + diesel (orm) + serde_derive to make a rest api. Currently, I'm dealing with error handling for the api if diesel fails to insert a user for whatever reason. It looks like this:
pub fn create(user: InsertableUser, connection: &MysqlConnection) -> ApiResponse {
let result = diesel::insert_into(users::table)
.values(&InsertableUser::hashed_user(user))
.execute(connection);
match result {
Ok(_) => ApiResponse {
json: json!({"success": true, "error": null}),
status: Status::Ok,
},
Err(error) => {
println!("Cannot create the recipe: {:?}", error);
ApiResponse {
json: json!({"success": false, "error": error}),
status: Status::UnprocessableEntity,
}
}
}
}
However, json: json!({"success": false, "error": error}), gives me this error:
the trait bound `diesel::result::Error: user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not satisfied
the trait `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not implemented for `diesel::result::Error`
note: required because of the requirements on the impl of `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` for `&diesel::result::Error`
note: required by `serde_json::value::to_value`rustc(E0277)
<::serde_json::macros::json_internal macros>(123, 27): the trait `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not implemented for `diesel::result::Error`
By the sounds of it, diesel::result::Error does not #[derive(Serialize)], and so cannot be serialized with the json! macro. Thus, I need some way to make the diesel::result::Error implement/derive Serialize.
Thanks in advance for any help.
BTW the ApiResponse looks like:
use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response;
use rocket::response::{Responder, Response};
use rocket_contrib::json::JsonValue;
#[derive(Debug)]
pub struct ApiResponse {
pub json: JsonValue,
pub status: Status,
}
impl<'r> Responder<'r> for ApiResponse {
fn respond_to(self, req: &Request) -> response::Result<'r> {
Response::build_from(self.json.respond_to(&req).unwrap())
.status(self.status)
.header(ContentType::JSON)
.ok()
}
}
Serde provides a workaround for deriving serialization implementations for external crates - see the section Derive for remote crates in their documentation.
You have to define an enum with the same definition as the one you are trying to serialize (diesel::result::Error in your case), and then identify that as a kind of proxy for the type you are trying to serialize, like this:
#[derive(Serialize, Deserialize)]
#[serde(remote = "diesel::result::Error")]
struct ErrorDef {
// Definition in here the same as the enum diesel::result::Error
// ...
}
Of course you would have to do the same for all of the types enclosed within the Error type as well (or at least any types that don't already implement Serialize).
The documentation states that Serde checks the definition you provide against the one in the 'remote' crate, and throws an error if they differ, which would help keep them in sync.
Also note that this does not result in diesel::result::Error implementing Serialize - rather you now have a stand-in type that you can use like this:
struct JsonErrorRespone {
pub success: bool,
#[serde(with = "ErrorDef")]
pub error: diesel::result::Error,
}
You would then serialize an instance of the above struct instead of your existing json! macro call.
Alternatively the document linked above also gives some tips for manually calling the correct Serialize / Deserialize implementations.
Disclaimer: I haven't used this facility yet, the above was gleaned from the documentation only.
The short answer: yes you can. Personally I've always found it a bit difficult.
As a compromise, you could pull out just the parts of the error that are relevant to you, or even do:
ApiResponse {
json: json!({"success": false, "error": error.to_string() }),
status: Status::UnprocessableEntity,
}
If you're content with just a textual representation of the error.

Can't see enum value

I'm trying to recreate the way error handling is done in std::io. I'm basing my code on this article.
My problem is that I've put my code in a separate mod and now I can't see the enum value I want to return. Code sample:
mod error {
use std::str::SendStr;
pub type ProgramResult<T> = Result<T, ProgramError>;
#[deriving(Show)]
pub struct ProgramError {
kind: ProgramErrorKind,
message: SendStr
}
/// The kinds of errors that can happen in our program.
/// We'll be able to pattern match against these.
#[deriving(Show)]
pub enum ProgramErrorKind {
Configuration
}
impl ProgramError {
pub fn new<T: IntoMaybeOwned<'static>>(msg: T, kind: ProgramErrorKind) -> ProgramError {
ProgramError {
kind: kind,
message: msg.into_maybe_owned()
}
}
}
}
I cannot see Configuration anywhere else in my code, even though the enum is public and duly imported in all other mods that try to use this. Any ideas?
Are you using the module resolution operator when attempting to reference the enum types? For instance, here's an example that works fine:
mod error {
#[deriving(Show)]
pub enum ProgramErrorKind {
Configuration,
SomethingElse
}
}
fn main() {
// You can import them locally with 'use'
use error::Configuration;
let err = Configuration;
// Alternatively, use '::' directly
let res = match err {
error::Configuration => "Config error!",
error::SomethingElse => "I have no idea.",
};
println!("Error type: {}", res);
}

Resources