我应该如何处理 NEAR 协议中的智能合约结构变更?

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

假设我有合同

...
pub struct Contract {
  collection_a: Vector<String>,
}

部署此版本后,当我更改智能合约的数据结构时,例如

pub struct Contract {
  collection_a: Vector<String>,
  collection_b: Vector<String>,
}

我运行在与合约交互时遇到错误

       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]
    }
  }
}

当我需要更新结构时,如何处理这种情况?

你要的是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(),
    }
  }
}

首先使用此代码更新合同,然后使用合同密钥调用 migrate 方法。在下次升级时,您可以删除方法和 OldContract 结构以节省存储空间。

您可能 运行 遇到的一个问题是不适合单个块的存储迁移。 AFAIK,没有解决方案。然而,Borsh 序列化是确定性的,因此只要您将数据结构保持为枚举,您就应该能够重新解释当前链存储并将迁移拆分为多个部分迁移。确保彻底测试,你 运行 不可恢复地搞砸你的合同状态的风险。