Skip to main content

Substrate With WASM

Acurast can be used on Substrate with an WASM integration. The output of the Acurast Jobs are pushed to a smart contract deployed in the WASM environment, that can then be used by other developers.

Example integration with WASM smart contract parachain

The following example shows a possible integration approach for a WASM smart contract parachain (using pallet-contracts). Similarly to the EVM integration, the example shows how to route the fulfillment's payload to a smart contract by calling the fulfill method on it and passing the payload bytes as argument.

#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Eq)]
pub enum ContractMethodSelector {
Default,
Custom([u8; 4]),
}

impl ContractMethodSelector {
fn into_fixed_bytes(self) -> [u8; 4] {
match self {
Self::Default => BlakeTwo256::hash(b"fulfill").as_bytes()[0..4].try_into().unwrap(),
Self::Custom(bytes) => bytes,
}
}
}

#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Eq)]
pub struct RegistrationExtra {
pub contract_address: AccountId,
pub selector: ContractMethodSelector,
}

parameter_types! {
pub const MaxAllowedSources: u16 = 100;
pub AllowedRevocationListUpdate: Vec<AccountId> = vec![];
}

impl pallet_acurast::Config for Runtime {
type Event = Event;
type RegistrationExtra = RegistrationExtra;
type FulfillmentRouter = AcurastRouter;
...
}

pub struct AcurastRouter;
impl pallet_acurast::FulfillmentRouter<Runtime> for AcurastRouter {
fn received_fulfillment(
origin: frame_system::pallet_prelude::OriginFor<Runtime>,
from: <Runtime as frame_system::Config>::AccountId,
fulfillment: pallet_acurast::Fulfillment,
registration: pallet_acurast::JobRegistrationFor<Runtime>,
requester: <<Runtime as frame_system::Config>::Lookup as StaticLookup>::Target,
) -> DispatchResultWithPostInfo {
Contracts::call(
origin,
registration.extra.contract_address.into(),
0,
18_750_000_000,
None,
[
registration.extra.selector.into_fixed_bytes().to_vec(),
from.encode(),
requester.encode(),
fulfillment.payload.encode(),
]
.concat(),
)
}
}

construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
// All your other pallets
...
// Contracts
Contracts: pallet_contracts,

// Acurast
Acurast: pallet_acurast,

}
);

ink! Contract Example

The following snippet of code shows a very basic WASM smart contract implemented using ink! and capable of receiving the routed fulfill call from the FulfillmentRouter implemented above:

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
mod receiver {

use ink_prelude::vec::Vec;

/// Defines the storage of your contract.
/// Add new fields to the below struct in order
/// to add new static storage fields to your contract.
#[ink(storage)]
pub struct Receiver {
source: Option<AccountId>,
target: Option<AccountId>,
payload: Option<Vec<u8>>,
}

impl Receiver {
/// Constructor that initializes `source`, `target` and `payload` to `None`.
///
/// Constructors can delegate to other constructors.
#[ink(constructor)]
pub fn default() -> Self {
Self {
source: None,
target: None,
payload: None,
}
}

/// Simply stores the `source`, `target` and `payload` values.
#[ink(message)]
pub fn fulfill(&mut self, source: AccountId, target: AccountId, payload: Vec<u8>) {
self.source = Some(source);
self.target = Some(target);
self.payload = Some(payload);
}

/// Simply returns the current value of our `source`, `target` and `payload`.
#[ink(message)]
pub fn get(&self) -> (Option<AccountId>, Option<AccountId>, Option<Vec<u8>>) {
(
self.source.clone(),
self.target.clone(),
self.payload.clone(),
)
}
}
}