use linera_base::{
abi::{ContractAbi, ServiceAbi},
data_types::{
Amount, ApplicationDescription, ApplicationPermissions, BlockHeight, Bytecode, Resources,
SendMessageRequest, Timestamp,
},
ensure, http,
identifiers::{
Account, AccountOwner, ApplicationId, ChainId, DataBlobHash, ModuleId, StreamName,
},
ownership::{
AccountPermissionError, ChainOwnership, ChangeApplicationPermissionsError,
ChangeOwnershipError, CloseChainError,
},
vm::VmRuntime,
};
use serde::Serialize;
use super::wit::{base_runtime_api as base_wit, contract_runtime_api as contract_wit};
use crate::{Contract, KeyValueStore, ViewStorageContext};
#[derive(Debug)]
pub struct ContractRuntime<Application>
where
Application: Contract,
{
application_parameters: Option<Application::Parameters>,
application_id: Option<ApplicationId<Application::Abi>>,
application_creator_chain_id: Option<ChainId>,
chain_id: Option<ChainId>,
block_height: Option<BlockHeight>,
message_is_bouncing: Option<Option<bool>>,
message_origin_chain_id: Option<Option<ChainId>>,
timestamp: Option<Timestamp>,
}
impl<Application> ContractRuntime<Application>
where
Application: Contract,
{
pub(crate) fn new() -> Self {
ContractRuntime {
application_parameters: None,
application_id: None,
application_creator_chain_id: None,
chain_id: None,
block_height: None,
message_is_bouncing: None,
message_origin_chain_id: None,
timestamp: None,
}
}
pub fn key_value_store(&self) -> KeyValueStore {
KeyValueStore::for_contracts()
}
pub fn root_view_storage_context(&self) -> ViewStorageContext {
ViewStorageContext::new_unchecked(self.key_value_store(), Vec::new(), ())
}
}
impl<Application> ContractRuntime<Application>
where
Application: Contract,
{
pub fn application_parameters(&mut self) -> Application::Parameters {
self.application_parameters
.get_or_insert_with(|| {
let bytes = base_wit::application_parameters();
serde_json::from_slice(&bytes)
.expect("Application parameters must be deserializable")
})
.clone()
}
pub fn application_id(&mut self) -> ApplicationId<Application::Abi> {
*self
.application_id
.get_or_insert_with(|| ApplicationId::from(base_wit::get_application_id()).with_abi())
}
pub fn application_creator_chain_id(&mut self) -> ChainId {
*self
.application_creator_chain_id
.get_or_insert_with(|| base_wit::get_application_creator_chain_id().into())
}
pub fn read_application_description(
&mut self,
application_id: ApplicationId,
) -> ApplicationDescription {
base_wit::read_application_description(application_id.forget_abi().into()).into()
}
pub fn chain_id(&mut self) -> ChainId {
*self
.chain_id
.get_or_insert_with(|| base_wit::get_chain_id().into())
}
pub fn block_height(&mut self) -> BlockHeight {
*self
.block_height
.get_or_insert_with(|| base_wit::get_block_height().into())
}
pub fn system_time(&mut self) -> Timestamp {
*self
.timestamp
.get_or_insert_with(|| base_wit::read_system_timestamp().into())
}
pub fn chain_balance(&mut self) -> Amount {
base_wit::read_chain_balance().into()
}
pub fn owner_balance(&mut self, owner: AccountOwner) -> Amount {
base_wit::read_owner_balance(owner.into()).into()
}
pub fn chain_ownership(&mut self) -> ChainOwnership {
base_wit::get_chain_ownership().into()
}
pub fn application_permissions(&mut self) -> ApplicationPermissions {
base_wit::get_application_permissions().into()
}
pub fn http_request(&mut self, request: http::Request) -> http::Response {
base_wit::perform_http_request(&request.into()).into()
}
pub fn assert_before(&mut self, timestamp: Timestamp) {
base_wit::assert_before(timestamp.into());
}
pub fn read_data_blob(&mut self, hash: DataBlobHash) -> Vec<u8> {
base_wit::read_data_blob(hash.into())
}
pub fn assert_data_blob_exists(&mut self, hash: DataBlobHash) {
base_wit::assert_data_blob_exists(hash.into())
}
}
impl<Application> ContractRuntime<Application>
where
Application: Contract,
{
pub fn authenticated_signer(&mut self) -> Option<AccountOwner> {
contract_wit::authenticated_signer().map(AccountOwner::from)
}
pub fn message_is_bouncing(&mut self) -> Option<bool> {
*self
.message_is_bouncing
.get_or_insert_with(contract_wit::message_is_bouncing)
}
pub fn message_origin_chain_id(&mut self) -> Option<ChainId> {
*self
.message_origin_chain_id
.get_or_insert_with(|| contract_wit::message_origin_chain_id().map(ChainId::from))
}
pub fn authenticated_caller_id(&mut self) -> Option<ApplicationId> {
contract_wit::authenticated_caller_id().map(ApplicationId::from)
}
pub fn check_account_permission(
&mut self,
owner: AccountOwner,
) -> Result<(), AccountPermissionError> {
ensure!(
self.authenticated_signer() == Some(owner)
|| self.authenticated_caller_id().map(AccountOwner::from) == Some(owner),
AccountPermissionError::NotPermitted(owner)
);
Ok(())
}
pub fn send_message(&mut self, destination: ChainId, message: Application::Message) {
self.prepare_message(message).send_to(destination)
}
pub fn prepare_message(
&mut self,
message: Application::Message,
) -> MessageBuilder<Application::Message> {
MessageBuilder::new(message)
}
pub fn transfer(&mut self, source: AccountOwner, destination: Account, amount: Amount) {
contract_wit::transfer(source.into(), destination.into(), amount.into())
}
pub fn claim(&mut self, source: Account, destination: Account, amount: Amount) {
contract_wit::claim(source.into(), destination.into(), amount.into())
}
pub fn call_application<A: ContractAbi + Send>(
&mut self,
authenticated: bool,
application: ApplicationId<A>,
call: &A::Operation,
) -> A::Response
{
let call_bytes = A::serialize_operation(call)
.expect("Failed to serialize `Operation` in cross-application call");
let response_bytes = contract_wit::try_call_application(
authenticated,
application.forget_abi().into(),
&call_bytes,
);
A::deserialize_response(response_bytes)
.expect("Failed to deserialize `Response` in cross-application call")
}
pub fn emit(&mut self, name: StreamName, value: &Application::EventValue) -> u32 {
contract_wit::emit(
&name.into(),
&bcs::to_bytes(value).expect("Failed to serialize event"),
)
}
pub fn read_event(
&mut self,
chain_id: ChainId,
name: StreamName,
index: u32,
) -> Application::EventValue {
let event = contract_wit::read_event(chain_id.into(), &name.into(), index);
bcs::from_bytes(&event).expect("Failed to deserialize event")
}
pub fn subscribe_to_events(
&mut self,
chain_id: ChainId,
application_id: ApplicationId,
name: StreamName,
) {
contract_wit::subscribe_to_events(chain_id.into(), application_id.into(), &name.into())
}
pub fn unsubscribe_from_events(
&mut self,
chain_id: ChainId,
application_id: ApplicationId,
name: StreamName,
) {
contract_wit::unsubscribe_from_events(chain_id.into(), application_id.into(), &name.into())
}
pub fn query_service<A: ServiceAbi + Send>(
&mut self,
application_id: ApplicationId<A>,
query: A::Query,
) -> A::QueryResponse {
let query = serde_json::to_vec(&query).expect("Failed to serialize service query");
let response = contract_wit::query_service(application_id.forget_abi().into(), &query);
serde_json::from_slice(&response).expect("Failed to deserialize service response")
}
pub fn open_chain(
&mut self,
chain_ownership: ChainOwnership,
application_permissions: ApplicationPermissions,
balance: Amount,
) -> ChainId {
let chain_id = contract_wit::open_chain(
&chain_ownership.into(),
&application_permissions.into(),
balance.into(),
);
chain_id.into()
}
pub fn close_chain(&mut self) -> Result<(), CloseChainError> {
contract_wit::close_chain().map_err(|error| error.into())
}
pub fn change_ownership(
&mut self,
ownership: ChainOwnership,
) -> Result<(), ChangeOwnershipError> {
contract_wit::change_ownership(&ownership.into()).map_err(|error| error.into())
}
pub fn change_application_permissions(
&mut self,
application_permissions: ApplicationPermissions,
) -> Result<(), ChangeApplicationPermissionsError> {
contract_wit::change_application_permissions(&application_permissions.into())
.map_err(|error| error.into())
}
pub fn create_application<Abi, Parameters, InstantiationArgument>(
&mut self,
module_id: ModuleId,
parameters: &Parameters,
argument: &InstantiationArgument,
required_application_ids: Vec<ApplicationId>,
) -> ApplicationId<Abi>
where
Abi: ContractAbi,
Parameters: Serialize,
InstantiationArgument: Serialize,
{
let parameters = serde_json::to_vec(parameters)
.expect("Failed to serialize `Parameters` type for a cross-application call");
let argument = serde_json::to_vec(argument).expect(
"Failed to serialize `InstantiationArgument` type for a cross-application call",
);
let converted_application_ids: Vec<_> = required_application_ids
.into_iter()
.map(From::from)
.collect();
let application_id = contract_wit::create_application(
module_id.into(),
¶meters,
&argument,
&converted_application_ids,
);
ApplicationId::from(application_id).with_abi::<Abi>()
}
pub fn create_data_blob(&mut self, bytes: Vec<u8>) -> DataBlobHash {
let hash = contract_wit::create_data_blob(&bytes);
hash.into()
}
pub fn publish_module(
&mut self,
contract: Bytecode,
service: Bytecode,
vm_runtime: VmRuntime,
) -> ModuleId {
contract_wit::publish_module(&contract.into(), &service.into(), vm_runtime.into()).into()
}
pub fn validation_round(&mut self) -> Option<u32> {
contract_wit::validation_round()
}
}
#[must_use]
pub struct MessageBuilder<Message>
where
Message: Serialize,
{
authenticated: bool,
is_tracked: bool,
grant: Resources,
message: Message,
}
impl<Message> MessageBuilder<Message>
where
Message: Serialize,
{
pub(crate) fn new(message: Message) -> Self {
MessageBuilder {
authenticated: false,
is_tracked: false,
grant: Resources::default(),
message,
}
}
pub fn with_tracking(mut self) -> Self {
self.is_tracked = true;
self
}
pub fn with_authentication(mut self) -> Self {
self.authenticated = true;
self
}
pub fn with_grant(mut self, grant: Resources) -> Self {
self.grant = grant;
self
}
pub fn send_to(self, destination: ChainId) {
let serialized_message =
bcs::to_bytes(&self.message).expect("Failed to serialize message to be sent");
let raw_message = SendMessageRequest {
destination,
authenticated: self.authenticated,
is_tracked: self.is_tracked,
grant: self.grant,
message: serialized_message,
};
contract_wit::send_message(&raw_message.into())
}
}