Migrate contracts for concordium-std
8.1#
With the concordium_std
version 8.1
release, writing and reading smart contracts is easier than ever before.
The new version of the standard library reduces the need for generics and traits in your init and receive methods.
These generics and traits were there to support testing with the test_infrastructure
, which has been deprecated in favor of the concordium-smart-contract-testing
library.
This guide shows you how to migrate your contract code to use the simpler concrete types and use fewer generics.
Note
You should only migrate your contract code if you are also ready to migrate all your tests, that invoke init or receive methods directly, to use the concordium-smart-contract-testing
library.
Migrating init
methods#
To migrate a typical init
method you must change the following:
Remove the generic parameter
<S: HasStateApi>
on the init methodReplace the trait
&impl HasInitContext
with the concrete type&InitContext
Remove the generic parameter
<S>
on theStateBuilder
Here is an example:
/// Before
#[init(contract = "contract_before")]
fn init_before<S: HasStateApi>(
ctx: &impl HasInitContext,
state_builder: &mut StateBuilder<S>,
) -> InitResult<State> { todo!() }
/// After
#[init(contract = "contract_after")]
fn init_after( // `<S: HasStateApi>` removed
ctx: &InitContext, // `impl` and `Has` removed
state_builder: &mut StateBuilder, // `<S>` removed
) -> InitResult<State> { todo!() }
Migrating receive
methods#
To migrate a typical receive
method you must change the following:
Remove the generic parameter
<S: HasStateApi>
on the receive methodReplace the trait
&impl HasReceiveContext
with the concrete type&ReceiveContext
Replace the trait
&impl HasHost<..>
with the concrete type&Host
Remove the generic parameter
StateApiType = S
on theHost
Here is an example:
/// Before
#[receive(contract = "my_contract", name = "my_receive")]
fn receive_before<S: HasStateApi>(
ctx: &impl HasReceiveContext,
host: &impl HasHost<State, StateApiType = S>,
) -> ReceiveResult<MyReturnValue> { todo!() }
/// After
#[receive(contract = "my_contract", name = "my_receive")]
fn receive_after( // `<S: HasStateApi>` removed
ctx: &ReceiveContext, // `impl` and `Has` removed
host: &Host<State>, // `impl Has` and `, StateApiType = S removed
) -> ReceiveResult<MyReturnValue> { todo!() }
Migrating advanced state types#
If your contract state directly, or indirectly, contains one or more advanced state types, i.e., StateMap
, StateSet
, or StateBox
, then you also need to make a small adjustment.
The advanced state types are generic over not only the types stored, such as the keys and values in a map but also over a type that implements the HasStateApi
trait.
This is because the deprecated test_infrastructure
used a different implementation of the underlying contract state, i.e., a different implementation of HasStateApi
, than the Concordium nodes do.
But concordium-smart-contract-testing
uses the exact same state implementation as the nodes do and it is therefore possible to specify the concrete type, StateApi
, as the default.
Until the test_infrastructure
module is completely removed, the libraries will still support it, and thus the generic parameter S
must still be present.
To migrate an advanced state type you must change the following:
Set
StateApi
as the default type for the generic parameters that must implementHasStateApi
(typically namedS
)Remove the generic type parameter for the
HasStateApi
type where you use your state type, e.g.,MyState
instead ofMyState<S>
Here is an example of the change you must make:
/// Before
struct MyState<S> {
my_map: StateMap<AccountAddress, TokenCount, S>,
}
#[init(contract = "contract_before")]
fn init_before<S: HasStateApi>(
ctx: &impl HasInitContext,
state_builder: &mut StateBuilder<S>,
) -> InitResult<MyState<S>> {
Ok(MyState{ my_map: state_builder.new_map() })
}
/// After
struct MyState<S = StateApi> {
my_map: StateMap<AccountAddress, TokenCount, S>,
}
#[init(contract = "contract_before")]
fn init_before(
ctx: &InitContext,
state_builder: &mut StateBuilder,
) -> InitResult<MyState> {
Ok(MyState{ my_map: state_builder.new_map() })
}
Reference material for migrating types and tests#
The examples above show how to migrate most contracts, but for advanced contracts, there may be more types to migrate. The list below shows how to achieve that. It also includes types already described above:
&impl HasInitContext
becomes&InitContext
&impl HasReceiveContext
becomes&ReceiveContext
&mut StateBuilder<S>
becomes&mut StateBuilder
&impl HasHost<MyState, StateApiType = S>
becomes&Host<MyState>
&mut impl HasHost<MyState, StateApiType = S>
becomes&mut Host<MyState>
When using the
low_level
attribute:On inits:
&mut impl HasStateApi
becomes&mut StateApi
On receives:
&mut impl Host<S>
becomes&mut LowLevelHost
struct MyState<S> { my_map: StateMap<_,_, S> }
becomesstruct MyState<S = StateApi> { my_map: StateMap<_, _, S> }
&impl HasCryptoPrimitives
becomes&CryptoPrimitives
&impl HasChainMetadata
becomes&ChainMetadata
&mut impl HasLogger
becomes&mut Logger
To migrate your tests, read the how-to guide Integration test a contract in Rust.
You can also refer to the pull request, where our example contracts were rewritten.
This shows both the removal of generics and how to migrate tests from using test_infrastructure
to concordium-smart-contract-testing
.