Skip to content

Latest commit

 

History

History
125 lines (77 loc) · 8.29 KB

3.md

File metadata and controls

125 lines (77 loc) · 8.29 KB

Task 3: Universal Versioning Template

File with solution

High-Level Description

Develop a system facilitating easy updating or versioning of smart contracts. This system should serve as a template, wrapping the main logic of a smart contract to enable seamless handling of different versions. Note that in this context, "wrapping" refers to a smart contract encapsulating another, adding versioning functionality, and is not to be confused with contract wrappers in the Blueprint SDK.

Motivation

Understanding the motivation behind this task aids in its comprehension. Smart contracts are inherently static; once deployed, their code is immutable unless they are designed for code modification. Replacing code might suffice in simple scenarios, but often it's inadequate, especially when:

  • Smart contracts receive messages from other contracts. It's crucial to avoid processing old messages intended for a previous version with a new version, as message formats and data interpretation may differ.
  • The structure of data stored in the smart contract's storage might evolve.

Your goal is to create a "universal smart contract versioning template" addressing these issues, allowing any smart contract to become upgradeable as described. While ideally, users wouldn't need to worry about versioning, some simple modifications in their smart contracts are necessary to support it:

  • Use process_message instead of recv_internal for incoming message processing.
  • Retrieve contract data from the storage parameter in process_message, not through get_data().
  • Update contract data by returning a new data cell from process_message, rather than using set_data().
  • Replace get_data() with get_storage() in get methods.

External messages are excluded for simplicity.

Additionally, when the storage format changes, your wrapper must call a cell migrate_one(cell old_storage) method for storage format conversion (by first calling set_c3(migration_code) to access it). This allows the wrapper to automatically migrate the storage cell to the new format without altering the main contract logic.

Caution

This task, while resembling a real-life scenario, has been simplified for clarity. Notably, it lacks security measures—anyone could update the code to a new version. Real-world applications would require additional features and modifications for blockchain deployment.

Your Task

Implement a wrapper (template) with four methods:

  • recv_internal
    • Handles internal messages from users.
    • Checks if an update is necessary.
    • if an update is needed - it migrates the storage and updates the contract code; if the update is needed but update code isn't attached, it throws an error 200.
    • Executes process_message and updates storage.
  • cell get_storage() - Returns the main smart contract's unwrapped storage.
  • cell wrap_storage(int version_id, cell storage) - Wraps the main contract's storage with versioning data.
  • int version() method_id - Returns the smart contract's current version.

Refer to the 3.fc template for more details, including the required structure.

Important to note that code update and all processing of incoming message should be done in one transaction.

Deployment

To deploy a smart contract you have to compose its Stateinit, which includes the contract's code and data. Getting the code simply means compilation, but the data varies in this task based on how you store versioning data (e.g., version_id). For simplicity and generalization, let's assume that Stateinit's data should contain only the main smart contract data, as if it were deployed without versioning functionality. Your template must determine whether the contract is being invoked for the first time. If it is, the template should wrap the data in storage for future interactions.

Let's agree that the first call to the smart contract will occur with expected_version = 0, followed by an empty migrations dictionary. During this first call, your template must set the version to 1 for future interactions and finish the execution.

Inbound Message Structure

  1. The first 32 bits indicate the expected version of the contract.
  2. Next is a bit indicating whether update code is attached. If the bit is set to 1, it is followed by the update code in a reference.
  3. A dictionary containing migrations: from_version => MigrationPayload.
  4. payload, which is the pure payload passed to process_message. This payload is what the smart contract would receive if it were to operate without versioning functionality.

TL-B

_ new_version:uint32 migration_code:(Maybe ^Cell) = MigrationPayload;
_ expected_version:uint32 new_code:(Maybe ^Cell) migrations:(HashmapE 32 MigrationPayload) payload:^Cell = InternalMsgBody;

Migrations Explained

Imagine you have a smart contract at version 1, and you want to update it to version 4. In between these versions, there might be changes in how the contract stores its data (the "storage format"). To smoothly move from one version to another without losing or corrupting data, you need to update the storage format step by step by calling migrate_one function on storage data. This process is called "migration".

Migration Chain

Think of migration as a series of small steps to get from your current version to the desired version. For example, if your contract has versions 1, 2, 3, and 4, you can't jump directly from 1 to 4 without first considering what changes occurred in versions 2 and 3.

Let's break down a few scenarios:

  1. Simple Migration (1 -> 4):

    • If there are no storage format changes from versions 1 to 4, you can directly move from 1 to 4.
    • Example: 1 -> 4 with no storage migration needed.
  2. Step-by-Step Migration (1 -> 2 -> 3 -> 4):

    • If some intermediate versions have changes in storage format, you would need to update each of these in sequence.
    • Example: 1 -> 2 (no change), 2 -> 3 (migrate storage), 3 -> 4 (migrate storage).

The Migration Dictionary

When handling incoming messages in your wrapper (the extra layer you're adding for versioning), you will have a "migration dictionary". This is a list of which versions need storage migration. For example:

  • 3 -> 5: No storage migration
  • 5 -> 6: No storage migration
  • 6 -> 10: Migrate storage
  • 8 -> 9: No storage migration
  • 9 -> 10: No storage migration
  • 10 -> 11: Migrate storage

So, if you are migrating from version 3 to 11, you look at this dictionary and see which steps involve storage migration. In this case, you'll need to perform the migration for 6 -> 10 and 10 -> 11.

Error Handling

Your wrapper should also include checks for missing links in the migration chain. For instance, if there is an attempt to upgrade from version 1 to 4, but the migration dictionary only contains migrations for 1 -> 2 and 3 -> 4, it should identify this gap and throw an error 400. This is because it cannot manage the storage format changes that occur in the missing version.

Additionaly, it's important to ensure that both the current and the expected versions are included in the migrations dictionary provided in an incoming message. If either of these versions is not present in the dictionary, the wrapper should throw an error 400.

Storage

The main smart contract operates with "clean storage", unaware of versioning. Your template should manage the "wrapped storage", containing both clean storage and versioning data. get_storage should return the clean storage passed to process_message.

The important thing is that the way this "clean storage" behaves must be exactly the same as the regular storage would behave in the main smart contract without versioning functionality.

Rules

Follow these guidelines in your solution:

  • Don't alter the arguments or names of process_message and migrate_one.
  • Ensure get_storage returns the storage used in process_message.
  • The code separation indicated by <<<<< and >>>>> in the template is crucial. It is utilized by the testing system to evaluate your template with various smart contracts. Your final code must include these splitting comments, as shown in the provided example.

Quote

The whole secret lies in confusing the enemy, so that he cannot fathom our real intent.

― Sun Tzu, The Art of War