Skip to main content

Substrate With A WASM Smart Contract Environment

Acurast can be used to fulfill directly to smart contracts deployed on Substrate chains with a pallet-contracts integration.

Example integration with a WASM smart contract

The following example shows simple WASM smart contracts implemented with ink!.

Keep in mind that you can do much more with Acurast and get access to all modules besides these examples.

This example contract can store muliple prices for different symbols in the prices map. Furthermore, only processors that have their address added to the allow_list through the allow method will be able to provide a price.

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

#[ink::contract]
mod simple_oracle {
use ink::storage::Mapping;
use ink::prelude::vec::Vec;

#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
CallerNotInAllowList,
NotAdmin
}

pub type Result<T> = core::result::Result<T, Error>;

#[ink(storage)]
pub struct SimpleOracle {
prices: Mapping<Vec<u8>, u128>,
allow_list: Mapping<AccountId, ()>,
admin: Option<AccountId>,
}

impl SimpleOracle {

#[ink(constructor)]
pub fn new(admin: Option<AccountId>) -> Self {
Self {
prices: Mapping::new(),
allow_list: Mapping::new(),
admin,
}
}

#[ink(constructor)]
pub fn default() -> Self {
Self::new(None)
}

#[ink(message)]
pub fn fulfill(&mut self, values: Vec<(Vec<u8>, u128)>) -> Result<()> {
if !self.allow_list.contains(self.env().caller()) {
return Err(Error::CallerNotInAllowList);
}

for (key, value) in values {
self.prices.insert(key, &value);
}

Ok(())
}

#[ink(message)]
pub fn allow(&mut self, account: AccountId) -> Result<()> {
if let Some(admin) = self.admin {
if self.env().caller() != admin {
return Err(Error::NotAdmin)
}
}
self.allow_list.insert(account, &());
Ok(())
}

#[ink(message)]
pub fn disallow(&mut self, account: AccountId) -> Result<()> {
if let Some(admin) = self.admin {
if self.env().caller() != admin {
return Err(Error::NotAdmin)
}
}
self.allow_list.remove(account);
Ok(())
}

/// Returns the current value for given key.
#[ink(message)]
pub fn get(&self, key: Vec<u8>) -> Option<u128> {
self.prices.get(key)
}
}

#[cfg(test)]
mod tests {
/// Imports all the definitions from the outer scope so we can use them here.
use super::*;

/// We test if the default constructor does its job.
#[ink::test]
fn default_works() {
let simple_oracle = SimpleOracle::default();
assert_eq!(simple_oracle.get(Default::default()), None);
}

/// We test a simple use case of our contract.
#[ink::test]
fn it_works() {
let mut simple_oracle = SimpleOracle::new(None);
assert_eq!(simple_oracle.get(Default::default()), None);
simple_oracle.allow(AccountId::from([1; 32])).expect("can allow account");
simple_oracle.fulfill(vec![(vec![0], 10)]).expect("fulfill works");
assert_eq!(simple_oracle.get(vec![0]), Some(10));
}
}
}

Job Specification

Now that the contract has been deployed, we can prepare the script that will get executed by the Processor to provision the price feed or entropy.

Go to the Acurast Console to test and deploy your script, these templates can also be found there.

const callIndex = "<#CALL_INDEX#>"; // the call index for the 'call' extrinsic.
const destination = "<#CONTRACT_ADDRESS#>"; // contract address that will receive the 'fulfill' call.
_STD_.chains.substrate.signer.setSigner("SECP256K1"); // the type of signer used for sign the extrinsic call
const symbol = "AAVEBUSD";
httpGET(
"https://api.binance.com/api/v3/ticker/price?symbol=" + symbol,
{},
(response, _certificate) => {
const price = Number(JSON.parse(response)["price"]) * 10 ** 18;
const key = stringToHex(symbol);
const payload =
_STD_.chains.substrate.codec.encodeCompactUnsignedNumber(1) +
_STD_.chains.substrate.codec.encodeBytes(key) +
_STD_.chains.substrate.codec.encodeUnsignedNumber(price, 128);
_STD_.chains.substrate.contract.fulfill(
"<#NODE_URL#>",
callIndex,
destination,
payload,
{
refTime: "1470675162",
proofSize: "37458",
},
(opHash) => {
print("Succeeded: " + opHash);
},
(err) => {
print("Failed fulfill: " + err);
}
);
},
(err) => {
print("Failed get price: " + err);
}
);

function stringToHex(str) {
let hexString = "";
for (let i = 0; i < str.length; i++) {
let hex = str.charCodeAt(i).toString(16);
hexString += hex.length === 1 ? "0" + hex : hex;
}
return hexString;
}

Job Registration

Check out How to get started with the Acurast Console to register your Job.