Substrate With EVM
Acurast can be used on Substrate with an EVM integration. The output of the Acurast Jobs are pushed to a smart contract deployed in the EVM environment, that can then be used by other developers.
Example integration with EVM parachain
The following example shows a possible integration approach for an EVM parachain using frontier.
The example shows how to route the fulfillment's payload to a smart contract by calling the fulfill
mehod on it and passing the payload bytes are argument.
#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)]
pub enum MethodSignatureHash {
Default,
Custom(BoundedVec<u8, ConstU32<4>>),
}
impl MethodSignatureHash {
fn to_bytes(&self) -> [u8; 4] {
match self {
Self::Default => keccak_256!(b"fulfill(address,bytes)")[0..4].try_into().unwrap(),
Self::Custom(bytes) => bytes.to_vec().try_into().unwrap(),
}
}
}
#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)]
pub struct AcurastRegistrationExtra {
pub destination_contract: H160,
pub method_signature_hash: MethodSignatureHash,
}
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 {
let from_bytes: [u8; 32] = from.try_into().unwrap();
let eth_source = H160::from_slice(&from_bytes[0..20]);
let requester_bytes: [u8; 32] = requester.try_into().unwrap();
let eth_requester = H160::from_slice(&requester_bytes[0..20]);
let gas_limit = 4294967;
EVM::call(
origin,
eth_source,
registration.extra.destination_contract,
create_eth_call(
registration.extra.method_signature_hash,
eth_requester,
fulfillment.payload,
),
U256::zero(),
gas_limit,
DefaultBaseFeePerGas::get(),
None,
None,
vec![],
)
}
}
fn create_eth_call(method: MethodSignatureHash, requester: H160, payload: Vec<u8>) -> Vec<u8> {
let mut requester_bytes: [u8; 32] = [0; 32];
requester_bytes[(32 - requester.0.len())..].copy_from_slice(&requester.0);
let mut offset_bytes: [u8; 32] = [0; 32];
let payload_offset = requester_bytes.len().to_be_bytes();
offset_bytes[(32 - payload_offset.len())..].copy_from_slice(&payload_offset);
let mut payload_len_bytes: [u8; 32] = [0; 32];
let payload_len = payload.len().to_be_bytes();
payload_len_bytes[(32 - payload_len.len())..].copy_from_slice(&payload_len);
[
method.to_bytes().as_slice(),
requester_bytes.as_slice(),
offset_bytes.as_slice(),
payload_len_bytes.as_slice(),
&payload,
]
.concat()
}
impl pallet_acurast::Config for Runtime {
type Event = Event;
type RegistrationExtra = AcurastRegistrationExtra;
type FulfillmentRouter = AcurastRouter;
...
}
// Create the runtime by composing the FRAME pallets that were previously configured.
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
// All your other pallets
...
// EVM
Ethereum: pallet_ethereum::{Pallet, Call, Storage, Event, Config, Origin} = 50,
EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 51,
BaseFee: pallet_base_fee::{Pallet, Call, Storage, Config<T>, Event} = 52,
// Acurast
Acurast: pallet_acurast::{Pallet, Call, Storage, Event<T>} = 60,
}
);
Solidity Contract Example
The following snippet of code shows a very basic EVM smart contract capable of receiving the routed fulfill
call from the FulfillmentRouter
implemented above:
pragma solidity ^0.8.0;
contract SimpleFulfill {
address _address;
bytes _payload;
function fulfill(address addr, bytes memory payload) public {
_address = addr;
_payload = payload;
}
function getAddress() public view returns(address) {
return _address;
}
function getPayload() public view returns(bytes memory) {
return _payload;
}
}