Skip to content

Latest commit

 

History

History
938 lines (711 loc) · 32.3 KB

RELEASES.md

File metadata and controls

938 lines (711 loc) · 32.3 KB

Version 3.0-rc2 (2020-10-22)

This is the 2nd release candidate for pro! 3.0.

On top of the changes introduced in the first release candidate for pro! 3.0 we introduced the following improvements, new features and bug fixes:

  • The pro_storage crate now comes with a new BinaryHeap data structure that has a very similar interface to the well known Rust standard library BinaryHeap. It features specific optimizations to reduce the storage reads and writes required for its operations.
  • Fixed a bug with pro_storage::Lazy that corrupted the storage of other storage data structures if it was unused in a contract execution.
  • The pro_storage::alloc::Box type now implements tetsy_scale_info::TypeInfo which now allows it to be fully used inside other storage data structures such as pro_storage::collections::Vec. The missing of this implementation was considered a bug.
  • The LazyHashMap low-level storage abstraction is now re-exported from within the pro_storage::lazy module and docs are inlined.
  • Added note about the pro_core split into pro_env and pro_storage crates to the release notes of pro! 3.0-rc1.
  • The Cargo.toml documentation now properly lpros to the one deployed at docs.rs. On top of that crate level documentation for the pro_allocator crate has been added.
  • Add new ERC-20 example contract based on a trait implementation. Also modernized the old non-trait based ERC-20 example token contract.

Version 3.0-rc1 (2020-10-09)

Be prepared for the pro! 3.0 release notes because the whole version was basically a rewrite of all the major components that make up pro!. With our experience gained from previous releases of pro! we were able to detect weak spots of the design and provided pro! with more tools, more features and more efficiency as ever. Read more below …

Just. Be. Rust. 3.0

In the 3.0 update we further explored the space for pro! to just feel like it was plain Rust. With this in mind we changed the syntax slightly in order to better map from pro! to the generated Rust code. So what users see is mostly what will be generated by pro! later.

In this vein #[pro(storage)] and #[pro(event)] structs as well as #[pro(message)] and #[pro(constructor)] methods now need to be specified with public visibility (pub).

The #[pro(constructors)] syntax also changes and no longer uses a &mut self receiver but now follows the natural Rust constructors scheme. So it is no longer possible to shoot yourself in the foot by accidentally forgetting to initialize some important data structures.

Old pro! 2.0:

#[pro(constructor)]
fn new_erc20(&mut self, initial_supply: Balance) {
    let caller = self.env().caller();
    self.total_supply.set(initial_supply);
    self.balances.insert(caller, initial_supply);
}

New pro! 3.0:

#[pro(constructor)]
pub fn new_erc20(initial_supply: Balance) -> Self {
    let caller = self.env().caller();
    let mut balances = pro_storage::HashMap::new();
    balances.insert(caller, initial_supply);
    Self {
        total_supply: initial_supply,
        balances,
    }
}

Also pro! 3.0 no longer requires a mandatory version field in the header of the pro! module attribute.

Syntactically this is all it takes to port your current pro! smart contracts over to pro! 3.0 syntax.

Split of pro_core

The pro_core crate no longer exists. It has been split into the new pro_env and pro_storage crates.

Everything that was previously accessed through pro_core::env now lives in pro_env and everything that was previously accessed through pro_core::storage now lives in pro_storage. Both crates keep the responsibilities of their former originating pro_core modules.

New Storage Module

The storage module has been reworked entirely. Also it no longer lives in the pro_core crate but instead is defined as its own pro_storage crate.

In a sense it acts as the standard storage library for pro! smart contracts in that it provides all the necessary tools and data structures to organize and operate the contract's storage intuitively and efficiently.

Lazy

The most fundamental change in how you should thpro about data structures provided by the new pro_storage crate is that they are inherently lazy. We will explain what this means below! The pro_storage crate provides high-level and low-level lazy data structures. The difference between high-level and low-level lies in the distinction in how these data structures are aware of the elements that they operate on. For high-level data structures they are fully aware about the elements they contains, do all the clean-up by themselves so the user can concentrate on the business logic. For low-level data structures the responsibility about the elements lies in the hands of the contract author. Also they operate on cells (Option<T>) instead of entities of type T. But what does that mean exactly?

The new pro_storage::Lazy type is what corresponds the most to the old pro_core::storage::Value type. Both cache their entities and both act lazily on the storage. This means that a read or write operation is only performed when it really needs to in order to satisfy other inputs. Data types such as Rust primitives i32 or Rust's very own Vec or data structures can also be used to operate on the contract's storage, however, they will load their contents eagerly which is often not what you want.

An example follows with the below contract storage and a message that operates on either of the two fields.

#[pro(storage)]
pub struct TwoValues {
    offset: i32,
    a: i32,
    b: i32,
}

impl TwoValues {
    #[pro(message)]
    pub fn set(&mut self, which: bool, new_value: i32) {
        match which {
            true  => { self.a = self.offset + new_value; },
            false => { self.b = self.offset + new_value; },
        }
    }
}

Whenever we call TwoValues::set always both a and b are loaded despite the fact the we only operate on one of them at a time. This is very costly since storage accesses are in fact database look-ups. In order to prevent this eager loading of storage contents we can make use of pro_storage::Lazy or other lazy data structures defined in that crate:

#[pro(storage)]
pub struct TwoValues {
    offset: i32,
    a: pro_storage::Lazy<i32>,
    b: pro_storage::Lazy<i32>,
}

impl TwoValues {
    #[pro(message)]
    pub fn set(&mut self, which: bool, new_value: i32) {
        match which {
            true  => { self.a = offset + new_value; },
            false => { self.b = offset + new_value; },
        }
    }
}

Now a and b are only loaded when the contract really needs their values. Note that offset remained i32 since it is always needed and could spare the minor overhead of the pro_storage::Lazy wrapper.

HashMap

In the follow we explore the differences between the high-level pro_storage::collections::HashMap and the low-level pro_storage::lazy::LazyHashMap. Both provide very similar functionality in that they map some generic key to some storage entity.

However, their APIs look very different. Whereas the HashMap provides a rich and high-level API that is comparable to that of Rust's very own HashMap, the LazyHashMap provides only a fraction of the API and also operates on Option<T> values types instead of T directly. It is more similar Solidity mappings than to Rust's HashMap.

The fundamental difference of both data structures is that HashMap is aware of the keys that have been stored in it and thus can reconstruct exactly which elements and storage regions apply to it. This enables it to provide iteration and automated deletion as well as efficient way to defragment its underlying storage to free some storage space again. This goes very well in the vein of Substrate's storage rent model where contracts have to pay for the storage they are using.

Data Structure level of abstraction caching lazy element type container
T - yes no T primitive value
Lazy<T> high-level yes yes T single element container
LazyCell<T> low-level yes yes Option<T> single element, no container
Vec<T> high-level yes yes T Rust vector-like container
LazyIndexMap<T> low-level yes yes Option<T> similar to Solidity mapping
HashMap<K, V> high-level yes yes V (key type K) Rust map-like container
LazyHashMap<K, V> low-level yes yes Option<V> (key type K) similar to Solidity mapping

There are many more! For more information about the specifics please take a look into the pro_storage crate documentation.

Spread & Packed Modes

Storing or loading complex data structures to and from contract storage can be done in many different ways. You could store all information into a single storage cell or you could try to store all information into as many different cells as possible. Both strategies have pros and cons under different conditions.

For example it might be a very good idea to store all the information under the same cell if all the information is very compact. For example when we are dealing with a byte vector that is expected to never be larger than approx a thousand elements it would probably be more efficient if we store all those thousand bytes in the same cell and especially if we often access many of those (or all) in our contract messages.

On the other hand spreading information across as many cells as possible might be much more efficient if we are dealing with big data structures, a lot of information that is not compact, or when messages that operate on the data always only need a small fraction of the whole data. An example for this use case is if you have a vector of user accounts where each account stores potentially a lot of information, e.g. a 32-byte hash etc and where our messages only every operate on only a few of those at a time.

The pro_storage crate provides the user full control over the strategy or a mix of these two root strategies through some fundamental abstractions that we are briefly presenting to you.

Default: Spreading Mode

By default pro! spreads information to as many cells as possible. For example if you have the following #[pro(storage)] struct every field will live in its own single storage cell. Note that for c all 32 bytes will share the same cell!

#[pro(storage)]
pub struct Spreaded {
    a: i32,
    b: pro_storage::Lazy<i32>,
    c: [u8; 32],
}

Packing Storage

We can alter this behaviour by using the pro_storage::Pack abstraction:

pub struct Spreaded {
    a: i32,
    b: pro_storage::Lazy<i32>,
    c: [u8; 32],
}

#[pro(storage)]
pub struct Packed {
    packed: pro_storage::Pack<Spreaded>,
}

Now all fields of Spreaded will share the same storage cell. This means whenever one of them is stored to or loaded from the contract storage, all of them are stored or loaded. A user has to choose wisely what mode of operation is more suitable for their contract.

These abstractions can be combined in various ways, yielding full control to the users. For example, in the following only a and b share a common storage cell while c lives in its own:

pub struct Spreaded {
    a: i32,
    b: pro_storage::Lazy<i32>,
}

#[pro(storage)]
pub struct Packed {
    packed: pro_storage::Pack<Spreaded>,
    c: [u8; 32],
}

Spreading Array Cells

If we prefer to store all bytes of c into their own storage cell we can make use of the SmallVec data structure. The SmallVec is a high-level data structure that allows to efficiently organize a fixed number of elements similar to a Rust array. However, unlike a Rust array it acts lazily upon the storage and spreads its elements into different cells.

use typenum::U32;

pub struct Spreaded {
    a: i32,
    b: pro_storage::Lazy<i32>,
}

#[pro(storage)]
pub struct Packed {
    packed: pro_storage::Pack<Spreaded>,
    c: SmallVec<u8, U32>,
}

Opting-out of Storage

If you are in need of storing some temporary information across method and message boundaries pro! will have your back with the pro_storage::Memory abstraction. It allows you to simply opt-out of using the storage for the wrapped entity at all and thus is very similar to Solidity's very own memory annotation.

An example below:

#[pro(storage)]
pub struct OptedOut {
    a: i32,
    b: pro_storage::Lazy<i32>,
    c: pro_storage::Memory<i32>,
}

The the above example a and b are normal storage entities, however, c on the other hand side will never load from or store to contract storage and will always be reset to the default value of its i32 type for every contract call. It can be accessed from all pro! messages or methods via self.c but will never manipulate the contract storage and thus acts wonderfully as some shared local information.

Dynamic Storage Allocator

In the previous section we have seen how the default mode of operation is to spread information and how we can opt-in to packing information into single cells via pro_storage::Packed.

However, what if we wanted to store a vector of a vector of i32 for example? Naturally a user would try to construct this as follows:

use pro_storage::Vec as StorageVec;

#[pro(storage)]
pub struct Matrix {
    values: StorageVec<StorageVec<i32>>,
}

However, this will fail compilation with an error indicating that StorageVec<T> requires for its T to be packed (T: PackedLayout) which StorageVec<T> itself does not since it always stores all of its elements into different cells. The same applies to many other storage data structures provided by pro_storage and is a trade-off the pro! team decided for the case of efficiency of the overall system. Instead what a user can do in order to get their vector-of-vector to be working is to make use of pro!'s dynamic storage allocator capabilities.

For this the contract author has to first enable the feature via:

use pro_lang as pro;

#[pro::contract(dynamic_storage_allocator = true)]
mod matrix {
    // contract code ...
}

And then we can define our Matrix #[pro(storage)] as follows:

use pro_storage::{
    Vec as StorageVec,
    Box as StorageBox,
};

#[pro(storage)]
pub struct Matrix {
    values: StorageVec<StorageBox<StorageVec<i32>>>,
}

With pro_storage::Box<T> we can use a T: SpreadLayout as if it was T: PackedLayout since the pro_storage::Box<T> itself suffices the requirements and can be put into a single contract storage cell. The whole concept works quite similar to how Rust's Box works: by an indirection - contract authors are therefore advised to make use of dynamic storage allocator capabilities only if other ways of dealing with ones problems are not applicable.

Custom Data Sturctures

While the pro_storage crate provides tons of useful utilities and data structures to organize and manipulate the contract's storage contract authors are not limited by its capabilities. By implementing the core SpreadLayout and PackedLayout traits users are able to define their very own custom storage data structures with their own set of requirement and features that work along the pro_storage data structures as long as they fulfill the mere requirements stated by those two traits.

In the future we plan on providing some more pro! workshops and tutorials guiding the approach to design and implement a custom storage data structure.

In Summary

The new pro_storage crate provides everything you need to operate on your contract's storage. There are low-level and high-level data structures depending on your need of control. All provided data structures operate lazily on the contract's storage and cache their reads and writes for a more gas efficient storage access. Users should prefer high-level data structures found in the collections module over the low-level data structures found in the lazy module. For a list of all the new storage data structure visit pro_storage's documentation.

pro! Attributes

For pro! 3.0 we have added some more useful pro! specific attributes to the table. All of these pro! attributes are available to specify inside an pro! module. An pro! module is the module that is flagged by #[pro::contract] containing all the pro! definitions:

use pro_lang as pro;

#[pro::contract]
mod erc20 {
    #[pro(storage)]
    pub struct Erc20 { ... }

    impl Erc20 {
        #[pro(constructor)]
        pub fn new(initial_supply: Balance) -> Self { .. }

        #[pro(constructor)]
        pub fn total_supply(&self) -> Balance { .. }

        // etc. ...
    }
}

We won't be going into the details for any of those but will briefly present the entire set of pro! specific attributes below:

Attribute Where Applicable Description
#[pro(storage)] On struct definitions. Defines the pro! storage struct. There can only be one pro! storage definition per contract.
#[pro(event)] On struct definitions. Defines an pro! event. A contract can define multiple such pro! events.
#[pro(anonymous)] new Applicable to pro! events. Tells the pro! codegen to treat the pro! event as anonymous which omits the event signature as topic upon emitting. Very similar to anonymous events in Solidity.
#[pro(topic)] Applicate on pro! event field. Tells the pro! codegen to provide a topic hash for the given field. Every pro! event can only have a limited number of such topic field. Similar semantics as to indexed event arguments in Solidity.
#[pro(message)] Applicable to methods. Flags a method for the pro! storage struct as message making it available to the API for calling the contract.
#[pro(constructor)] Applicable to method. Flags a method for the pro! storage struct as constructor making it available to the API for instantiating the contract.
#[pro(payable)] new Applicable to pro! messages. Allows receiving value as part of the call of the pro! message. pro! constructors are implicitly payable.
#[pro(selector = "..")] new Applicable to pro! messages and pro! constructors. Specifies a concrete dispatch selector for the flagged entity. This allows a contract author to precisely control the selectors of their APIs making it possible to rename their API without breakage.
#[pro(namespace = "..")] new Applicable to pro! trait implementation blocks. Changes the resulting selectors of all the pro! messages and pro! constructors within the trait implementation. Allows to disambiguate between trait implementations with overlapping message or constructor names. Use only with great care and consideration!
#[pro(impl)] new Applicable to pro! implementation blocks. Tells the pro! codegen that some implementation block shall be granted access to pro! internals even without it containing any pro! messages or pro! constructors.

Merging of pro! Attributes

It is possible to merge attributes that share a common flagged entity. The example below demonstrates this for a payable message with a custom selector.

#[pro(message)]
#[pro(payable)]
#[pro(selector = "0xCAFEBABE")]
pub fn transfer(&mut self, from: AccountId, to: AccountId, value: Balance) -> Result<(), Error> {
    // actual implementation
}

We can also write the above pro! message definition in the following way:

#[pro(message, payable, selector = "0xCAFEBABE")]
pub fn transfer(&mut self, from: AccountId, to: AccountId, value: Balance) -> Result<(), Error> {
    // actual implementation
}

Trait Support

One of the most anticipated features of pro! 3.0 is its Rust trait support. Through the new #[pro::trait_definition] proc. macro it is now possible to define your very own trait definitions that are then implementable by pro! smart contracts.

This allows to define shared smart contract interfaces to different concrete implementations. Note that this pro! trait definition can be defined anywhere, even in another crate!

Example

Defined in the base_erc20.rs module.

use pro_lang as pro;

#[pro::trait_definition]
pub trait BaseErc20 {
    /// Creates a new ERC-20 contract and initializes it with the initial supply for the instantiator.
    #[pro(constructor)]
    fn new(initial_supply: Balance) -> Self;

    /// Returns the total supply.
    #[pro(message)]
    fn total_supply(&self) -> Balance;

    /// Transfers `amount` from caller to `to`.
    #[pro(message, payable)]
    fn transfer(&mut self, to: AccountId, amount: Balance);
}

An pro! smart contract definition can then implement this trait definition as follows:

use pro_lang as pro;

#[pro::contract]
mod erc20 {
    use base_erc20::BaseErc20;

    #[pro(storage)]
    pub struct Erc20 {
        total_supply: Balance,
        // more fields ...
    }

    impl BaseErc20 for Erc20 {
        #[pro(constructor)]
        fn new(initial_supply: Balance) -> Self {
            // implementation ...
        }

        #[pro(message)]
        fn total_supply(&self) -> Balance {
            // implementation ...
        }

        #[pro(message, payable)]
        fn transfer(&mut self, to: AccountId, amount: Balance) {
            // implementation ...
        }
    }
}

Calling the above Erc20 explicitely through its trait implementation can be done just as if it was normal Rust code:

// --- Instantiating the ERC-20 contract:
//
let mut erc20 = <Erc20 as BaseErc20>::new(1000);
// --- Is just the same as:
use base_erc20::BaseErc20;
let mut erc20 = Erc20::new(1000);

// --- Retrieving the total supply:
//
assert_eq!(<Erc20 as BaseErc20>::total_supply(&erc20), 1000);
// --- Is just the same as:
use base_erc20::BaseErc20;
assert_eq!(erc20.total_supply(), 1000);

There are still many limitations to pro! trait definitions and trait implementations. For example it is not possible to define associated constants or types or have default implemented methods. These limitations exist because of technical intricacies, however, please expect that many of those will be tackled in future pro! releases.

Version 2.1 (2020-03-25)

  • Add built-in support for cryptographic hashes:
    • Blake2 with 128-bit and 256-bit
    • Sha2 with 256-bit
    • Keccak with 256-bit
  • Add pro_core::hash module for high-level API to the new built-in hashes.
  • Update runtime-storage example pro! smart contract to demonstrate the new built-in hashes.

Version 2.0 (2019-12-03)

The pro! version 2.0 syntax has one major philosophy:

Just. Be. Rust.

To accomplish this, we take advantage of all the standard Rust types and structures and use attribute macros to tag these standard structures to be different parts of the pro! language.

Anything that is not tagged with an #[pro(...)] attribute tag is just standard Rust, and can be used in and out of your contract just like standard Rust could be used!

Every valid pro! contract is required to have at least one #[pro(constructor)], at least one #[pro(message)] and exactly one #[pro(storage)] attribute.

Follow the instructions below to understand how to migrate your pro! 1.0 contracts to this new pro! 2.0 syntax.

Update the pro! CLI

Install the latest pro! CLI using the following command:

cargo install --git https://github.com/tetcoin/cargo-contract cargo-contract --force

There is a new contract metadata format you need to use. You can generate the metadata using:

cargo contract generate-metadata

This will generate a file metadata.json you should upload when deploying or interacting with a contract.

Declaring a Contract

The fundamental change with the new pro! syntax is how we declare a new contract.

We used to wrap the whole pro! contract into a contract! macro. At that point, all syntax within the macro could be custom, and in our first iteration of the language, we used that in ways that made our code not really Rust anymore.

Now we wrap the whole contract in a standard Rust module, and include an attribute tag to identify this object as part of the pro! language. This means that all of our code from this point forward will be valid Rust!

Before After
contract! {
    ...
}
use pro_lang as pro;

#[pro::contract(version = "0.1.0")]
mod erc20 {
    ...
}

Note: we now require a mandatory pro! version in the header. You're welcome.

See the ERC20 example.

pro! Contract Tag

The pro! contract tag can be extended to provide other configuration information about your contract.

Defining Custom Types

We used to define types using a special #![env = DefaultSrmlTypes] tag.

Now we simply include the type definition in the #[pro::contract(...)] tag:

#[pro::contract(version = "0.1.0", env = MyCustomTypes)]

By default, we use DefaultSrmlTypes, so you don't need to define anything unless you plan to use custom types.

Dynamic Allocation

It is possible to enable the dynamic environment that allows for dynamic allocations by specifying dynamic_allocations = true in the parameters of the pro! header. This is disabled by default.

#[pro::contract(version = "0.1.0", dynamic_allocations = true)]

Note: The dynamic environment is still under research and not yet stable.

Declaring Storage

We define storage items just the same as before, but now we need to add the #[pro(storage)] attribute tag.

Before After
struct Erc20 {
    total_supply: storage::Value<Balance>,
    balances: storage::HashMap<AccountId, Balance>,
    allowances: storage::HashMap<(AccountId, AccountId), Balance>,
}
#[pro(storage)]
struct Erc20 {
    total_supply: storage::Value<Balance>,
    balances: storage::HashMap<AccountId, Balance>,
    allowances: storage::HashMap<(AccountId, AccountId), Balance>,
}

See the ERC20 example.

Declaring Events

To update your events, you need to:

  1. Change the old event keyword to a standard Rust struct.
  2. Add the #[pro(event)] attribute tag to your struct.

If you were previously indexing the items in your event with #[indexed]:

  1. Add the #[pro(topic)] attribute tag to each item in your event.
Before After
event Transfer {
    from: Option<AccountId>,
    to: Option<AccountId>,
    #[indexed]
    value: Balance,
}
#[pro(event)]
struct Transfer {
    from: Option<AccountId>,
    to: Option<AccountId>,
    #[pro(topic)]
    value: Balance,
}

See the ERC20 example.

Environment Handler

EnvHandler is no longer exposed to the user and instead the environment is now always accessed via self.env().

Before After

Getting the caller:

let caller = env.caller();

Emitting an event:

env.emit(...)

Getting the caller:

let caller = self.env().caller();

Emitting an event:

self.env().emit_event(...)

Note: The name of the function used to emit an event was updated to emit_event.

Message Functions

We used to use pub(external) to tag functions that could be called by the outside world.

We now simply add the attribute #[pro(message)].

Before After
pub(external) fn total_supply(&self) -> Balance {
    *self.total_supply
}
#[pro(message)]
fn total_supply(&self) -> Balance {
    *self.total_supply
}

See the ERC20 example.

Defining a Constructor

We used to define our constructor by implementing the Deploy trait and defining the deploy function.

But now our constructor function is in the same place as the rest of our contract functions, within the general implementation of the storage struct.

We tag these functions with the #[pro(constructor)] attribute. We can create multiple different constructors by simply creating more functions with the same tag. You can name a constructor function whatever you want (except starting with __pro which is reserved for all functions).

Before After
impl Deploy for Erc20 {
    fn deploy(&mut self, init_supply: Balance) {
        let caller = env.caller();
        self.total_supply.set(init_value);
        self.balances.insert(caller, init_supply);
        env.emit(Transfer {
            from: None,
            to: Some(env.caller()),
            value: init_value
        });
    }
}
impl Erc20 {
    #[pro(constructor)]
    fn new(&mut self, initial_supply: Balance) {
        let caller = self.env().caller();
        self.total_supply.set(initial_supply);
        self.balances.insert(caller, initial_supply);
        self.env().emit_event(Transferred {
            from: None,
            to: Some(caller),
            amount: initial_supply,
        });
    }
}

See the ERC20 example.

Cross Contract Calls

It is now possible to call pro! messages and pro! constructors. So pro! constructors allow delegation and pro! messages can easily call other pro! messages.

Given another pro! contract like mod Adder { ... }, we can call any of its functions:

use adder::Adder;
//--snip--
#[pro(storage)]
struct Delegator {
    adder: storage::Value<Adder>,
}
//--snip--
let result = self.adder.inc(by);

See the delegator example.

Factory Contracts

Creation of other contracts from a factory contract works pretty much the same way it did in the old pro! language.

However, users are now required to specify the code_hash separately rather than in the constructor:

.using_code(code_hash)

Also, they need to specify the used pro! environment (most likely self.env()):

create_using(self.env())
Before After
let accumulator = Accumulator::new(accumulator_code_hash, init_value)
    .value(total_balance / 4)
    .create()
    .expect("failed at instantiating the accumulator contract");
let accumulator = Accumulator::new(init_value)
    .value(total_balance / 4)
    .gas_limit(12345)
    .using_code(accumulator_code_hash)
    .create_using(self.env())
    .expect("failed at instantiating the `Accumulator` contract");

See the delegator example.

Contract Tests

Testing contracts off-chain is done by cargo test and users can simply use the standard routines of creating unit test modules within the pro! project:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn my_test() { ... }
}

Test instances of contracts can be created with something like:

let contract = MyContract::my_constructor(a, b);

Messages can simply be called on the returned instance as if MyContract::my_constructor returns a Self instance.

See the flipper example.

The off-chain test environment has lost a bit of power compared to the old pro! language.

It is not currently possible to query and set special test data about the environment (such as the caller of a function or amount of value sent), but these will be added back in the near future.

pro!-less Implementations

It is also possible to annotate an entire impl blocks with:

#[pro(impl)]
impl Contract {
    fn internal_function(&self) {
        self.env().emit_event(EventName);
    }
}.

This is useful if the impl block itself doesn't contain any pro! constructors or messages, but you still need to access some of the "magic" provided by pro!. In the example above, you would not have access to emit_event without #[pro(impl)].