Scrypto v0.3 release notes

On February 16th 2022 was release Scrypto v0.3. Listed on this page are changes that this new version introduces compared to v0.2. The most important change is the addition of the transaction manifest, allowing you to write and compose transactions in an easy to understand language. This also allows you to send complex types to your methods such as HashMaps and Vectors.

Transaction model

Added the transaction manifest

The transaction manifest is a file containing line by line instructions for executing a transaction atomically. A single manifest file can contain calls to many packages and components, and if an error occurs in the middle of the transaction, everything gets reverted to the initial state. It even allows you to add checks between component calls to make sure they returned at least a certain amount of resources. For all available commands, see this page. To give you an idea of the power of this new tool, look at this example where you buy a gumball for 10 XRD, swap it for XRD on RadiSwap, then put the XRD back into your account:

# Withdraw 10 XRD from your account
CALL_METHOD Address("<ACCOUNT_ADDRESS>") "withdraw"  Decimal("10.0")  Address("<XRD_ADDRESS>")  BucketRef(1u32);

# The 10 XRD are now on the worktop. Put them inside a bucket named "XRD"
TAKE_FROM_WORKTOP  Decimal("10.0") Address("<XRD_ADDRESS>") Bucket("XRD");

# Call the `buy_gumball` method and pass the bucket containing 10 XRD as argument
CALL_METHOD Address("<GUMBALL_MACHINE_ADDRESS>") "buy_gumball" Bucket("XRD");

# The gumball is now on the worktop. Put it inside a bucket named "GUM"
TAKE_FROM_WORKTOP Decimal("1.0") Address("<GUMBALL_RESSOURCE_ADDRESS>") Bucket("GUM");

# Swap the GUM for XRD
CALL_METHOD Address("<RADISWAP_ADDRESS>") "swap" Bucket("GUM");

# The swapped XRD is now on the worktop. The following method takes all resources on the worktop, put each into different buckets and then send them to the specified component method. In our case, we send them to the account component.
CALL_METHOD_WITH_ALL_RESOURCES Address("<ACCOUNT_ADDRESS>") "deposit_batch";

Added subcommands to resim

To support the transaction manifest, we added two new commands to resim. The first allows you to run a manifest file: resim run <manifest_file>. The second allows you to create a new transaction manifest from the call-method and call-function commands instead of running the actual transaction:

resim call-function <package_address> Hello new --manifest my_manifest.txt

Added macro to simplify Decimal creation

Since floating point numbers are not allowed in Scrypto, it was hard to create Decimal of specific values. We simplified the process by creating macros to help instantiate Decimal and BigDecimal numbers easily:

use scrypto::dec;
use scrypto::bdec;

// From string notation
let my_number: Decimal = dec!("210.345");

// From exponent notation
let my_number: Decimal = dec!(210345, -3);

// You can also create BigDecimals:
let my_big_number: BigDecimal = bdec!("1000000000000000000000000.1234");

Changed the Context to access current blueprint/component address

You can now get the currently running blueprint or component address through Context::actor(). If you run this method inside a function, you will get the current blueprint’s package address and name. If you run this inside a method, you will get the component’s address.

pub fn function_call() {
  info!("{:?}", Context::actor());
  //Blueprint(0147dfe3a58e8a630305f2f3df82949cd70ce49e2cde097b259f8d, "Test")
}

pub fn method_call(&self) {
  info!("{:?}", Context::actor());
  //Component(02afe53b9981dddb1b4ae44ff09deeae68f5bf42b360d88eaeb077)
}

Removed access to transaction signers

To push people toward the resource oriented way of writing smart contracts and to stop using the public key to associate resources to a particular account, we removed the public key from the Context. We are encouraging to, instead, use the user badge pattern to authenticate and identify your users.

Removed the Account API

The Account API, allowing you to do things like Account::from(address).deposit(bucket);, was created to make interacting with the Account component easier. A problem with an approach like this, is that it made many developers think that the account was not actually a Component. You now have to use the Account component just like any other components. For example, to send funds to an Account, you now have to manually call the deposit method on the account’s component:

pub fn call(bucket: Bucket) {
  let address = Address::from_str("0293c50278...6ac0c00a").unwrap();
  Component::from(address).call::<()>("deposit", vec![scrypto_encode(&bucket)]);
}

Added check for mut keyword for Buckets and ResourceDefs

We are now enforcing the usage of the mut keyword on buckets of which you take or put resources and ResourceDefs that you mint or burn resources of:

pub fn mint_example() -> (Bucket, Bucket) {
    let admin = ResourceBuilder::new_fungible(DIVISIBILITY_NONE)
        .initial_supply_fungible(1);

    // Notice the `mut` keyword here
    let mut resource_def: ResourceDef = ResourceBuilder::new_fungible(DIVISIBILITY_NONE)
        .flags(MINTABLE)
        .badge(admin.resource_address(), MAY_MINT)
        .no_initial_supply();

    // We need it because we are changing its supply here
    (resource_def.mint(1000, admin.present()), admin)
}

// Here we need `mut` in front of `self` because we are updating its vault and in front of `bucket` because we are updating its content
pub fn take_example(&mut self, mut bucket: Bucket) -> Bucket {
    self.vault.put(bucket.take(10));
    bucket
}

Changed BucketRefs to automatically drop

We have removed the need to call the drop method on BucketRefs before the end of a method. This is now done automatically for you.

Changed the way we specify ids for non-fungible resources

We enabled more flexibility when defining the Keys of your non-fungible resources. You can now use any* variable type as the ID of a non-fungible resource, but you need to wrap it inside a NonFungibleKey:

// Random u128 as key
nft_def.mint_non_fungible(
  &NonFungibleKey::from(Uuid::generate()),
  data,
  auth
);

// u8 as key
nft_def.mint_non_fungible(
  &NonFungibleKey::from(3u8),
  data,
  auth
);

Because of their popularity, most people associate NFTs with image artwork. We wanted to make sure Scrypto developers don’t think they are limited to creating non-fungible resources that represent images. So we renamed all the methods and objects related to NFTs to make this distinction more clear.

Vaults

v0.2

v0.3

vault.take_nft(&key)

vault.take_non_fungible(&key)

vault.take_nft_with_auth(&key, auth)

vault.take_non_fungible_with_auth(&key, auth)

vault.get_nfts()

vault.get_non_fungibles()

vault.get_nft_ids()

vault.get_non_fungible_keys()

vault.get_nft_id()

vault.get_non_fungible_key()

vault.get_nft_data(key)

vault.get_non_fungible_data(key)

vault.update_nft_data(key, new_data, auth)

vault.update_non_fungible_data(key, new_data, auth)

Buckets and BucketRefs

v0.2 v0.3

bucket.get_nft_ids()

bucket.get_non_fungible_keys()

bucket.get_nft_id()

bucket.get_non_fungible_key()

bucket.take_nft(key)

bucket.take_non_fungible(key)

bucket.get_nfts()

bucket.get_non_fungibles()

bucket.get_nft_ids()

bucket.get_non_fungible_keys()

bucket.get_nft_id()

bucket.get_non_fungible_key()

bucket.get_nft_data(key)

bucket.get_non_fungible_data(key)

bucket.update_nft_data(key, new_data, auth)

bucket.update_non_fungible_data(key, new_data, auth)

ResourceDefs

v0.2 v0.3

resource_def.mint_nft(id, data, auth)

resource_def.mint_non_fungible(key, data, auth)

resource_def.get_nft_data(id)

resource_def.get_non_fungible_data(key)

resource_def.update_nft_data(id, new_data, auth)

resource_def.update_non_fungible_data(key, new_data, auth)

Other

We also renamed the NFTData macro to NonFungibleData:

// v0.2
#[derive(NFTData)]
pub struct Ticket {
    pub row: u32,
    pub column: u32,
}

// v0.3
#[derive(NonFungibleData)]
pub struct Ticket {
    pub row: u32,
    pub column: u32,
}

Testing

Changed name of InMemoryLedger

We renamed the InMemoryLedger to InMemorySubstateStore.

Changed signature of TransactionExecutor

The signature of TransactionExecutor::new() changed from

pub fn new(ledger: &'l mut L, current_epoch: u64, nonce: u64) -> Self

to

pub fn new(ledger: &'l mut L, trace: bool) -> Self

To update the epoch or the nonce, you now have to call the set_epoch(epoch) and increase_nonce() methods on executor.ledger_mut():

let mut ledger = InMemorySubstateStore::with_bootstrap();
let mut executor = TransactionExecutor::new(&mut ledger, true);

executor.ledger_mut().set_epoch(10);
executor.ledger_mut().increase_nonce();

Now that we specify the trace parameter on initialization, you don’t have to specify it every time you call the TransactionExecutor::run(transaction) method.

Changed the Receipt model

We made some changes to the Receipt object, returned from the TransactionExecutor::run() method. The most important change is that we replaced the boolean success with result that is of type Result. To know if a transaction succeeded, you should now use:

receipt.result.is_ok();