Contract Finalization
When a transaction finishes executing successfully, there's a final step where
all loaded application contracts are allowed to finalize
, similarly to
executing a destructor. The default implementation of finalize
just persists
the application's state:
/// Finishes the execution of the current transaction.
async fn finalize(&mut self) -> Result<(), Self::Error> {
Self::Storage::store(self.state_mut()).await;
Ok(())
}
Applications may want to override the finalize
method, which allows performing
some clean-up operations after execution finished. While finalizing, contracts
may send messages, read and write to the state, but are not allowed to call
other applications, because they are all also finalizing.
If
finalize
is overriden, the default implementation is not executed, so the developer must ensure that the application's state is persisted correctly.
While finalizing, contracts can force the transaction to fail by panicking or
returning an error. The block is then rejected, even if the entire transaction's
operation had succeeded before finalize
was called. This allows a contract to
reject transactions if other applications don't follow any required constraints
it establishes after it responds to a cross-application call.
As an example, a contract that executes a cross-application call with
Operation::StartSession
may require the same caller to perform another
cross-application call with Operation::EndSession
before the transaction ends.
pub struct MyContract {
state: MyState;
runtime: ContractRuntime<Self>;
active_sessions: HashSet<ApplicationId>;
}
#[async_trait]
impl Contract for MyContract {
type Error = MyError;
type State = MyState;
type Storage = ViewStateStorage<Self>;
type Message = ();
async fn new(state: Self::State, runtime: ContractRuntime<Self>) -> Result<Self, Self::Error> {
MyContract {
state,
runtime,
active_sessions: HashSet::new(),
}
}
fn state_mut(&mut self) -> &mut Self::State {
&mut self.state
}
async fn initialize(
&mut self,
argument: Self::InitializationArgument,
) -> Result<(), Self::Error> {
Ok(())
}
async fn execute_operation(
&mut self,
operation: Self::Operation,
) -> Result<Self::Response, Self::Error> {
let caller = self.runtime
.authenticated_caller_id()
.expect("Missing caller ID");
match operation {
Operation::StartSession => {
assert!(
self.active_sessions.insert(caller_id),
"Can't start more than one session for the same caller"
);
}
Operation::EndSession => {
assert!(
self.active_sessions.remove(&caller_id),
"Session was not started"
);
}
}
}
async fn execute_message(&mut self, message: Self::Message) -> Result<(), Self::Error> {
unreachable!("This example doesn't support messages");
}
async fn finalize(&mut self) -> Result<(), Self::Error> {
assert!(
self.active_sessions.is_empty(),
"Some sessions have not ended"
);
Self::Storage::store(self.state_mut()).await;
Ok(())
}
}