Migrating from Scrypto v0.4.0 to v0.5.0

This article provides a comprehensive guide on the changes that you need to make to your packages to migrate them from Scrypto v0.4.0 to Scrypto v0.5.0. A majority of the changes required are to tests and the transaction manifest. Aside from that, your blueprints should require some minimal changes for the migration.

This migration guide assumes that you have already installed and updated your Scrypto toolchain to the current version. If you have not, then please follow the instructions here to update to the new version.

Throughout this article you will find that sample code is provided for v0.3.0 and v0.4.0 of Scrypto. These are provided to help the reader compare and contrast the changes between the two versions and to help make the new concepts clearer.

Updating your Cargo.toml file

When you create a new Scrypto package through scrypto new-package your-package-name a number of files are automatically generated for you. One of these files is the Cargo.toml file which references the dependencies to include in your package as well as their versions, meaning that you will find the Cargo.toml files inside the package directories.

In order to target the correct version of Scrypto when compiling and publishing your packages, you need to change the version of the Scrypto toolchain specified in the Cargo.toml file of your packages to be v0.5.0 instead of v0.4.0. In addition to that, you will need to add the additional dev-dependencies used in v0.5.0.

In addition to changing the dependency versions, you need to add overflow-checks = true to the [profile.release] section. This compilation flag is currently the default for all new packages created through the Scrypto CLI as of v0.5.0. This flag means that your package will check for overflows when performing any arithmetic and would panic if any overflows occur.

  • Scrypto v0.5.0

  • Scrypto v0.4.0

[package]
name = "your-package-name"
version = "0.1.0"
edition = "2021"

[dependencies]
sbor = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.5.0" } (1)
scrypto = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.5.0" } (1)

[dev-dependencies]
radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.5.0" } (1)
transaction = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.5.0" } (2)
scrypto-unit = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.5.0" } (2)

[profile.release]
opt-level = 's'        # Optimize for size.
lto = true             # Enable Link Time Optimization.
codegen-units = 1      # Reduce number of codegen units to increase optimizations.
panic = 'abort'        # Abort on panic.
strip = "debuginfo"    # Strip debug info.
overflow-checks = true # Panic in the case of an overflow. (3)

[lib]
crate-type = ["cdylib", "lib"]
1 The version of these dependencies needed to be updated from v0.4.0 to v0.5.0.
2 These are new dependencies that you need to have in your project which are used when writing tests.
3 This is a new flag that is added by default to all packages created after the release of v0.5.0. This flag tells the compiler that the package should panic if an overflow occurs.
[package]
name = "your-package-name"
version = "0.1.0"
edition = "2021"

[dependencies]
sbor = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.4.0" }
scrypto = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.4.0" }

[dev-dependencies]
radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.4.0" }

[profile.release]
opt-level = 's'     # Optimize for size.
lto = true          # Enable Link Time Optimization.
codegen-units = 1   # Reduce number of codegen units to increase optimizations.
panic = 'abort'     # Abort on panic.
strip = "debuginfo" # Strip debug info.

[lib]
crate-type = ["cdylib", "lib"]

Component Instantiation and Globalization

With the introduction of local components in this version of Scrypto, the way in which components are instantiated and globalized has changed slightly such that it follows a non-consuming builder pattern.

In the case when you wish to create and globalize a component without adding any access rules, everything remains unchanged when it comes to this process.

  • Scrypto v0.5.0

  • Scrypto v0.4.0

use scrypto::prelude::*;

blueprint! {
    struct Sample {}

    impl Sample {
        pub fn new() -> ComponentAddress {
            return Self{}
                .instantiate()
                .globalize();
        }
    }
}
use scrypto::prelude::*;

blueprint! {
    struct Sample {}

    impl Sample {
        pub fn new() -> ComponentAddress {
            return Self{}
                .instantiate()
                .globalize();
        }
    }
}

However, when you wish to add access rules prior to globalization, the syntax for that has changed slightly. The adding of access rules can no longer be chained with instantiation and globalization. Instead, it has to happen in an individual step.

  • Scrypto v0.5.0

  • Scrypto v0.4.0

use scrypto::prelude::*;

blueprint! {
    struct Sample {}

    impl Sample {
        pub fn new() -> ComponentAddress {
            // Setting up the auth
            let admin_badge: ResourceAddress = ...;
            let auth: AccessRules = AccessRules::new()
                .method("authenticated_method", rule!(require(admin_badge)))
                .default(rule!(allow_all));

            let mut sample_component: SampleComponent = Self{}.instantiate(); (1)
            sample_component.add_access_check(auth); (2)
            sample_component.globalize() (3)
        }

        pub fn authenticated_method(&self) {
            // Some code goes here.
        }
    }
}
1 This creates a local component of the type SampleComponent. This local component is mutable as the next line adds access checks to the local component.
2 Adds access checks to the local component. The add_access_check method takes a mutable reference to self and not ownership of self. In addition to that, it does not return anything. Therefore, it can no longer be chained with the other method calls.
3 This globalizes the local component and returns the component address of the globalized component.
use scrypto::prelude::*;

blueprint! {
    struct Sample {}

    impl Sample {
        pub fn new() -> ComponentAddress {
            // Setting up the auth
            let admin_badge: ResourceAddress = ...;
            let auth: AccessRules = AccessRules::new()
                .method("authenticated_method", rule!(require(admin_badge)))
                .default(rule!(allow_all));

            return Self{}
                .instantiate()
                .add_access_check(auth)
                .globalize();
        }

        pub fn authenticated_method(&self) {
            // Some code goes here.
        }
    }
}

Resource Manager Mutability

In previous versions of Scrypto, there was no need to have a mutable reference to a ResourceManager when performing operations such as minting, burning, and so on. However, with the release of 0.5.0, any operations such as minting, burning, updating of metadata, updating of resource behavior, and so on, require that a mutable reference to a ResourceManager is used.

  • Scrypto v0.5.0

  • Scrypto v0.4.0

let resource_manager: &mut ResourceManager = borrow_resource_manager!(my_token); (1)
let tokens: Bucket = self.my_badge.authorize(|| resource_manager.mint(1));
1 A mutable reference to a ResourceManager is now used since we will be minting this resource.
let resource_manager: &ResourceManager = borrow_resource_manager!(my_token);
let tokens: Bucket = self.my_badge.authorize(|| resource_manager.mint(1));

Transaction Manifest

With v0.5.0, there have been a number of user-facing changes made to the transaction manifest format. The following is a summary of the changes that you need to know about for the migration from v0.4.0.

  • Transactions now require that a fee is locked. Therefore, all manifests now need to start with an instruction to lock the fee.

  • The manifest no longer has separate types for types such as TreeSet and HashSet. Instead, in the manifest they are represented simply as Set. Similar changes have come to "map" types and "list" types as well.

  • Dropping of all proofs has been decoupled from the CALL_METHOD_WITH_ALL_RESOURCES and made into its own DROP_ALL_PROOFS instruction.

The following is an example of two transaction manifests from v0.4.0 and v0.5.0 that showcases the changes that you need to make to allow your v0.4.0 manifests to work in v0.5.0.

  • Scrypto v0.5.0

  • Scrypto v0.4.0

CALL_METHOD (1)
    ComponentAddress("account_sim1qdcqwq4fnmg70s4dwh4v8ekaavf0uwcw46m29drl3xwsknv53y")
    "lock_fee"
    Decimal("10");

CALL_METHOD
    ComponentAddress("account_sim1qdcqwq4fnmg70s4dwh4v8ekaavf0uwcw46m29drl3xwsknv53y")
    "create_proof_by_ids"
    ResourceAddress("resource_sim1qz08n5clnuxv3q5tu3cu346weyuccx69y2kq0xgkmq0qynk4jh")
    Set<NonFungibleId>(NonFungibleId("00009000"), NonFungibleId("00008000")); (2)

CALL_METHOD
    ComponentAddress("component_sim1qteyt2230upc88dklg89as0zj0z6pjdr96hdkh9nvn4s5wtpfq")
    "submit_data"
    List<u64>(12u64, 18u64) (3)
    Map<String, Decimal>("XRD", Decimal("12"), "BTC", Decimal("19")); (4)

DROP_ALL_PROOFS; (5)
CALL_METHOD_WITH_ALL_RESOURCES
    ComponentAddress("account_sim1qdcqwq4fnmg70s4dwh4v8ekaavf0uwcw46m29drl3xwsknv53y")
    "deposit_batch";
1 This instruction locks a fee from the user’s account which will be used throughout this transaction. By the end of the transaction, remaining unused XRD is given back to the user.
2 Instead of using a TreeSet type, we use a generic Set type which is understood by the Radix Engine.
3 Instead of using a Vec type, we use a generic List type which is understood by the Radix Engine.
4 Instead of using a HashMap type, we use a generic Map type which is understood by the Radix Engine.
5 The CALL_METHOD_WITH_ALL_RESOURCES instruction no longer performs an automatic drop of all proofs. Instead, you need to call the DROP_ALL_PROOFS instruction.
CALL_METHOD
    ComponentAddress("03fe83004e1ab4d903521c380d6758c4634aa995b9e2793728eb7b")
    "create_proof_by_ids"
    ResourceAddress("026ee1a35cdfaf031a3826dd0ecc652c4006e30c159c57342c9086")
    TreeSet<NonFungibleId>(NonFungibleId("00009000"), NonFungibleId("00008000"));

CALL_METHOD
    ComponentAddress("03d5853ca809c8fdb9cb46bf67273187142a2aa73706c8f947038b")
    "submit_data"
    Vec<u64>(12u64, 18u64)
    HashMap<String, Decimal>("XRD", Decimal("12"), "BTC", Decimal("19"));

CALL_METHOD_WITH_ALL_RESOURCES
    ComponentAddress("03fe83004e1ab4d903521c380d6758c4634aa995b9e2793728eb7b")
    "deposit_batch";

Writing Tests

The way in which tests are written has been changed to allow for a simpler and more convenient way of writing tests. Currently, most of the logic used in tests exists in the TestRunner class in the scrypto-unit crate which is one of the new dependencies introduced in 0.5.0. The code snippet below compares the way that tests were written in 0.4.0 with that of 0.5.0 and highlights a number of the key differences and changes that you need to make to migrate your tests.

  • Scrypto v0.5.0

  • Scrypto v0.4.0

use radix_engine::ledger::*;
use radix_engine::model::extract_package;
use scrypto::core::NetworkDefinition;
use scrypto::prelude::*;
use scrypto::args;
use scrypto_unit::*;
use transaction::builder::ManifestBuilder;

#[test]
fn test_hello() {
    // Setup the environment
    let mut store = TypedInMemorySubstateStore::with_bootstrap(); (1)
    let mut test_runner = TestRunner::new(true, &mut store); (2)

    // Create an account
    let (public_key, _private_key, account_component) = test_runner.new_account(); (3)

    // Publish package
    let package_address = test_runner
        .publish_package(extract_package(compile_package!())
        .unwrap()); (4)

    // Test the `instantiate_hello` function.
    let manifest = ManifestBuilder::new(&NetworkDefinition::local_simulator()) (5) (6)
        .call_function(package_address, "Hello", "instantiate_hello", args!())
        .build(); (7)
    let receipt = test_runner.execute_manifest_ignoring_fee(manifest, vec![public_key]); (7)
    println!("{:?}\n", receipt);
    receipt.expect_success(); (8)
    let component = receipt.new_component_addresses[0];

    // Test the `free_token` method.
    let manifest = ManifestBuilder::new(&NetworkDefinition::local_simulator())
        .call_method(component, "free_token", args!())
        .call_method_with_all_resources(account_component, "deposit_batch")
        .build();
    let receipt = test_runner.execute_manifest_ignoring_fee(manifest, vec![public_key]);
    println!("{:?}\n", receipt);
    receipt.expect_success();
}
1 The InMemorySubstateStore had its name changed to TypedInMemorySubstateStore.
2 This is the TestRunner that will be used to run the transactions. The TestRunner abstracts the complexity of transactions away from the user.
3 Accounts can be created through the TestRunner.
4 Packages are published through the TestRunner.
5 The name of the TransactionBuilder has been changed to ManifestBuilder.
6 The ManifestBuilder requires a network for which the manifest is being built for.
7 The build method no longer requires the public key of the signers. Instead, they are provided to the execute_manifest* methods in the TestRunner.
8 The way to check that a transaction has been successful has changed from receipt.result.is_ok() to receipt.expect_success().
use radix_engine::ledger::*;
use radix_engine::transaction::*;
use scrypto::prelude::*;

#[test]
fn test_hello() {
    // Set up environment.
    let mut ledger = InMemorySubstateStore::with_bootstrap();
    let mut executor = TransactionExecutor::new(&mut ledger, false);
    let (pk, sk, account) = executor.new_account();
    let package = executor.publish_package(compile_package!()).unwrap();

    // Test the `instantiate_hello` function.
    let transaction1 = TransactionBuilder::new()
        .call_function(package, "Hello", "instantiate_hello", args!())
        .build(executor.get_nonce([pk]))
        .sign([&sk]);
    let receipt1 = executor.validate_and_execute(&transaction1).unwrap();
    println!("{:?}\n", receipt1);
    assert!(receipt1.result.is_ok());

    // Test the `free_token` method.
    let component = receipt1.new_component_addresses[0];
    let transaction2 = TransactionBuilder::new()
        .call_method(component, "free_token", args!())
        .call_method_with_all_resources(account, "deposit_batch")
        .build(executor.get_nonce([pk]))
        .sign([&sk]);
    let receipt2 = executor.validate_and_execute(&transaction2).unwrap();
    println!("{:?}\n", receipt2);
    assert!(receipt2.result.is_ok());
}

ABIs and Cross Package Calls

The ABI format that is used by the import! macro has had some slight changes made to it with the release of v0.5.0. The following code snippet shows the difference between the v0.4.0 and v0.5.0 style ABIs.

  • Scrypto v0.5.0

  • Scrypto v0.4.0

{
  "package_address": "package_sim1q8uak7psyh3q2qfv3fyjgdcjtjvm9xnw2q6wxhp8qujsuek50n",
  "blueprint_name": "Hello",
  "abi": {
    "structure": {
      "type": "Struct",
      "name": "Hello",
      "fields": {
        "type": "Named",
        "named": [
          [
            "sample_vault",
            {
              "type": "Custom",
              "type_id": 179,
              "generics": []
            }
          ]
        ]
      }
    },
    "fns": [
      {
        "ident": "instantiate_hello",
        "mutability": null,
        "input": {
          "type": "Struct",
          "name": "Hello_instantiate_hello_Input",
          "fields": {
            "type": "Named",
            "named": []
          }
        },
        "output": {
          "type": "Custom",
          "type_id": 129,
          "generics": []
        },
        "export_name": "Hello_instantiate_hello"
      },
      {
        "ident": "free_token",
        "mutability": "Mutable",
        "input": {
          "type": "Struct",
          "name": "Hello_free_token_Input",
          "fields": {
            "type": "Named",
            "named": []
          }
        },
        "output": {
          "type": "Custom",
          "type_id": 177,
          "generics": []
        },
        "export_name": "Hello_free_token"
      }
    ]
  }
}
{
  "package_address": "01e39b904affd4219a34fb12879e4545d881cd57c3670749f29ea8",
  "blueprint_name": "Hello",
  "functions": [
    {
      "name": "instantiate_hello",
      "inputs": [],
      "output": {
        "type": "Custom",
        "name": "ComponentAddress",
        "generics": []
      }
    }
  ],
  "methods": [
    {
      "name": "free_token",
      "mutability": "Mutable",
      "inputs": [],
      "output": {
        "type": "Custom",
        "name": "Bucket",
        "generics": []
      }
    }
  ]
}

As you can see from the snippet above, the changes required to the ABI are somewhat complex to do by hand. So, to migrate your ABIs from 0.4.0 to 0.5.0, simply export the ABI once again through

resim export-abi $package_address $blueprint_name