#[macro_export]
macro_rules! resource_address {
($s:expr) => {
$crate::macros::_macro_exports::ResourceAddress::from_hex($s).expect("Failed to parse resource string")
};
}
pub mod _macro_exports {
pub use tari_ootle_common_types::{SubstateRequirement, engine_types::substate::SubstateId};
pub use tari_ootle_transaction::{
self as transaction,
TransactionBuilder,
UnsignedTransaction,
builder::named_args::{IntoArg, NamedArg},
};
pub use tari_template_lib_types::{Amount, ComponentAddress, ResourceAddress, TemplateAddress};
pub use crate::{
builtin_templates::{
UnsignedTransactionBuilder,
component::{ComponentInterface, ComponentInvokeBuilder, IntoBuildParts, OotleInvoke, TemplateInterface},
},
provider::{Provider, ProviderError, WantInput},
};
}
#[macro_export]
macro_rules! ootle_template {
(
template $name:ident {
$($item:tt)*
}
) => {
$crate::__ootle_template_inner!(@parse $name [] [] $($item)*);
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __ootle_template_inner {
(@parse $name:ident
[$($tpl:tt)*]
[$($cmp:tt)*]
fn $method:ident(& self $(, $param:ident: $ptype:ty)*) $(-> $ret:ty)?;
$($rest:tt)*
) => {
$crate::__ootle_template_inner!(@parse $name
[$($tpl)*]
[$($cmp)* { $method ($($param: $ptype),*) }]
$($rest)*
);
};
(@parse $name:ident
[$($tpl:tt)*]
[$($cmp:tt)*]
fn $method:ident(&mut self $(, $param:ident: $ptype:ty)*) $(-> $ret:ty)?;
$($rest:tt)*
) => {
$crate::__ootle_template_inner!(@parse $name
[$($tpl)*]
[$($cmp)* { $method ($($param: $ptype),*) }]
$($rest)*
);
};
(@parse $name:ident
[$($tpl:tt)*]
[$($cmp:tt)*]
fn $func:ident($($param:ident: $ptype:ty),* $(,)?) $(-> $ret:ty)?;
$($rest:tt)*
) => {
$crate::__ootle_template_inner!(@parse $name
[$($tpl)* { $func ($($param: $ptype),*) }]
[$($cmp)*]
$($rest)*
);
};
(@parse $name:ident
[$({ $func:ident ($($fp:ident: $ft:ty),*) })*]
[$({ $method:ident ($($mp:ident: $mt:ty),*) })*]
) => {
pub struct $name<'a, P, I> {
interface: I,
builder: $crate::macros::_macro_exports::ComponentInvokeBuilder<'a, P>,
}
#[allow(dead_code)]
impl<'a, P: $crate::macros::_macro_exports::Provider>
$name<'a, P, $crate::macros::_macro_exports::ComponentInterface>
{
pub fn for_component(
component: $crate::macros::_macro_exports::ComponentAddress,
provider: &'a P,
) -> Self {
Self {
interface: $crate::macros::_macro_exports::ComponentInterface { component },
builder: $crate::macros::_macro_exports::ComponentInvokeBuilder::new(provider),
}
}
pub fn component_address(&self) -> $crate::macros::_macro_exports::ComponentAddress {
self.interface.component
}
$(
pub fn $method(
self,
$($mp: impl $crate::macros::_macro_exports::IntoArg),*
) -> Self {
let Self { interface, builder } = self;
Self {
builder: builder.call_method(
interface.component,
stringify!($method),
vec![$($crate::macros::_macro_exports::IntoArg::into_arg($mp)),*],
),
interface,
}
}
)*
}
impl<'a, P: $crate::macros::_macro_exports::Provider>
$crate::macros::_macro_exports::IntoBuildParts
for $name<'a, P, $crate::macros::_macro_exports::ComponentInterface>
{
fn into_build_parts(self) -> (
$crate::macros::_macro_exports::TransactionBuilder,
std::collections::HashSet<$crate::macros::_macro_exports::WantInput>,
) {
$crate::macros::_macro_exports::IntoBuildParts::into_build_parts(self.builder)
}
}
impl<'a, P: $crate::macros::_macro_exports::Provider>
$crate::macros::_macro_exports::OotleInvoke
for $name<'a, P, $crate::macros::_macro_exports::ComponentInterface>
{
$crate::__ootle_invoke_impl!();
}
impl<'a, P: $crate::macros::_macro_exports::Provider>
$crate::macros::_macro_exports::UnsignedTransactionBuilder
for $name<'a, P, $crate::macros::_macro_exports::ComponentInterface>
{
$crate::__ootle_unsigned_tx_builder_impl!();
}
#[allow(dead_code)]
impl<'a, P: $crate::macros::_macro_exports::Provider>
$name<'a, P, $crate::macros::_macro_exports::TemplateInterface>
{
pub fn for_template(
template: $crate::macros::_macro_exports::TemplateAddress,
provider: &'a P,
) -> Self {
Self {
interface: $crate::macros::_macro_exports::TemplateInterface { template },
builder: $crate::macros::_macro_exports::ComponentInvokeBuilder::new(provider),
}
}
pub fn template_address(&self) -> $crate::macros::_macro_exports::TemplateAddress {
self.interface.template
}
$(
pub fn $func(
self,
$($fp: impl $crate::macros::_macro_exports::IntoArg),*
) -> Self {
let Self { interface, builder } = self;
Self {
builder: builder.call_function(
interface.template,
stringify!($func),
vec![$($crate::macros::_macro_exports::IntoArg::into_arg($fp)),*],
),
interface,
}
}
)*
}
impl<'a, P: $crate::macros::_macro_exports::Provider>
$crate::macros::_macro_exports::IntoBuildParts
for $name<'a, P, $crate::macros::_macro_exports::TemplateInterface>
{
fn into_build_parts(self) -> (
$crate::macros::_macro_exports::TransactionBuilder,
std::collections::HashSet<$crate::macros::_macro_exports::WantInput>,
) {
$crate::macros::_macro_exports::IntoBuildParts::into_build_parts(self.builder)
}
}
impl<'a, P: $crate::macros::_macro_exports::Provider>
$crate::macros::_macro_exports::OotleInvoke
for $name<'a, P, $crate::macros::_macro_exports::TemplateInterface>
{
$crate::__ootle_invoke_impl!();
}
impl<'a, P: $crate::macros::_macro_exports::Provider>
$crate::macros::_macro_exports::UnsignedTransactionBuilder
for $name<'a, P, $crate::macros::_macro_exports::TemplateInterface>
{
$crate::__ootle_unsigned_tx_builder_impl!();
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __ootle_invoke_impl {
() => {
fn pay_fee<A: Into<$crate::macros::_macro_exports::Amount>>(self, amount: A) -> Self {
let Self { interface, builder } = self;
Self {
builder: $crate::macros::_macro_exports::OotleInvoke::pay_fee(builder, amount),
interface,
}
}
fn want_vault_for(
self,
component_address: $crate::macros::_macro_exports::ComponentAddress,
resource_address: $crate::macros::_macro_exports::ResourceAddress,
required: bool,
) -> Self {
let Self { interface, builder } = self;
Self {
builder: $crate::macros::_macro_exports::OotleInvoke::want_vault_for(
builder,
component_address,
resource_address,
required,
),
interface,
}
}
fn want_substate(self, substate_id: $crate::macros::_macro_exports::SubstateId, required: bool) -> Self {
let Self { interface, builder } = self;
Self {
builder: $crate::macros::_macro_exports::OotleInvoke::want_substate(builder, substate_id, required),
interface,
}
}
fn want_all_vaults(self, component_address: $crate::macros::_macro_exports::ComponentAddress) -> Self {
let Self { interface, builder } = self;
Self {
builder: $crate::macros::_macro_exports::OotleInvoke::want_all_vaults(builder, component_address),
interface,
}
}
fn put_last_instruction_output_on_workspace<T: Into<String>>(self, label: T) -> Self {
let Self { interface, builder } = self;
Self {
builder: $crate::macros::_macro_exports::OotleInvoke::put_last_instruction_output_on_workspace(
builder, label,
),
interface,
}
}
fn add_input<S: Into<$crate::macros::_macro_exports::SubstateRequirement>>(self, substate_id: S) -> Self {
let Self { interface, builder } = self;
Self {
builder: $crate::macros::_macro_exports::OotleInvoke::add_input(builder, substate_id),
interface,
}
}
fn then<
F: FnOnce(
$crate::macros::_macro_exports::TransactionBuilder,
) -> $crate::macros::_macro_exports::TransactionBuilder,
>(
self,
f: F,
) -> Self {
let Self { interface, builder } = self;
Self {
builder: $crate::macros::_macro_exports::OotleInvoke::then(builder, f),
interface,
}
}
fn chain<B: $crate::macros::_macro_exports::IntoBuildParts>(self, other: B) -> Self {
let Self { interface, builder } = self;
Self {
builder: $crate::macros::_macro_exports::OotleInvoke::chain(builder, other),
interface,
}
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __ootle_unsigned_tx_builder_impl {
() => {
fn default_signer_address(&self) -> &$crate::Address {
$crate::macros::_macro_exports::UnsignedTransactionBuilder::default_signer_address(&self.builder)
}
fn add_input<S: Into<$crate::macros::_macro_exports::SubstateRequirement>>(self, substate_id: S) -> Self {
$crate::macros::_macro_exports::OotleInvoke::add_input(self, substate_id)
}
async fn prepare(
self,
) -> Result<$crate::macros::_macro_exports::UnsignedTransaction, $crate::macros::_macro_exports::ProviderError>
{
$crate::macros::_macro_exports::UnsignedTransactionBuilder::prepare(self.builder).await
}
};
}
#[macro_export]
macro_rules! const_nonzero_u64 {
($val:expr) => {{
const __NONZERO: core::num::NonZeroU64 = core::num::NonZeroU64::new($val).expect("Value must be non-zero");
__NONZERO
}};
}
#[cfg(test)]
mod tests {
use tari_template_lib_types::Amount;
use crate::builtin_templates::component::OotleInvoke;
#[test]
fn it_generates_a_non_zero() {
const NZ: core::num::NonZeroU64 = const_nonzero_u64!(5);
assert_eq!(NZ.get(), 5);
}
ootle_template! {
template TestStableCoin {
fn instantiate(initial_supply: Amount);
fn increase_supply(&mut self, amount: Amount);
fn decrease_supply(&mut self, amount: Amount);
fn withdraw(&mut self, amount: Amount);
fn deposit(&mut self);
fn get_balance(&self);
}
}
mod mock_provider {
use std::{
collections::{HashMap, HashSet},
sync::Weak,
};
use tari_ootle_common_types::{
Network,
engine_types::substate::{Substate, SubstateId},
};
use tari_ootle_transaction::UnsignedTransaction;
use crate::{
Address,
provider::{Provider, ProviderResult, WantInput},
};
pub struct MockProvider {
pub address: Address,
}
impl Provider for MockProvider {
type Client = ();
fn network(&self) -> Network {
Network::LocalNet
}
fn weak_client(&self) -> Weak<Self::Client> {
Weak::new()
}
fn default_signer_address(&self) -> &Address {
&self.address
}
async fn resolve_input_want_list(
&self,
transaction: UnsignedTransaction,
_want_list: &HashSet<WantInput>,
) -> ProviderResult<UnsignedTransaction> {
Ok(transaction)
}
async fn fetch_substates<I: IntoIterator<Item = SubstateId> + Send>(
&self,
_substate_ids: I,
) -> ProviderResult<HashMap<SubstateId, Substate>> {
Ok(HashMap::new())
}
}
}
#[test]
fn ootle_template_macro_generates_component_methods() {
use crate::keys::OotleSecretKey;
let secret = OotleSecretKey::random(tari_ootle_common_types::Network::LocalNet);
let provider = mock_provider::MockProvider {
address: secret.to_address(),
};
let component = tari_template_lib_types::ComponentAddress::new([0u8; 32].into());
let coin = TestStableCoin::for_component(component, &provider);
assert_eq!(coin.component_address(), component);
let coin = TestStableCoin::for_component(component, &provider);
let coin = coin.increase_supply(Amount::new(1000));
let coin = coin.decrease_supply(Amount::new(500));
let _coin = coin.pay_fee(1000u64);
}
ootle_template! {
template TestAccount {
fn deposit(&mut self);
fn withdraw(&mut self, amount: Amount);
}
}
#[test]
fn ootle_template_chain_across_templates() {
use crate::keys::OotleSecretKey;
let secret = OotleSecretKey::random(tari_ootle_common_types::Network::LocalNet);
let provider = mock_provider::MockProvider {
address: secret.to_address(),
};
let component_a = tari_template_lib_types::ComponentAddress::new([0u8; 32].into());
let component_b = tari_template_lib_types::ComponentAddress::new([1u8; 32].into());
let coin = TestStableCoin::for_component(component_a, &provider);
let _coin = coin
.withdraw(Amount::new(1000))
.put_last_instruction_output_on_workspace("bucket")
.chain(TestAccount::for_component(component_b, &provider).withdraw(Amount::new(500)))
.pay_fee(1000u64);
}
#[test]
fn ootle_template_macro_generates_template_functions() {
use crate::keys::OotleSecretKey;
let secret = OotleSecretKey::random(tari_ootle_common_types::Network::LocalNet);
let provider = mock_provider::MockProvider {
address: secret.to_address(),
};
let template = tari_template_lib_types::TemplateAddress::from_array([1u8; 32]);
let tpl = TestStableCoin::for_template(template, &provider);
assert_eq!(tpl.template_address(), template);
let tpl = TestStableCoin::for_template(template, &provider);
let _tpl = tpl.instantiate(Amount::new(1_000_000)).pay_fee(1000u64);
}
}