Skip to content

Feat: add swap extension #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ members = [
"pvq-executor",
"pvq-extension-core",
"pvq-extension-fungibles",
"pvq-extension-swap",
"pvq-extension",
"pvq-primitives",
"pvq-runtime-api",
Expand All @@ -37,6 +38,7 @@ pvq-program-metadata-gen = { path = "pvq-program-metadata-gen" }
pvq-executor = { path = "pvq-executor", default-features = false }
pvq-extension-core = { path = "pvq-extension-core", default-features = false }
pvq-extension-fungibles = { path = "pvq-extension-fungibles", default-features = false }
pvq-extension-swap = { path = "pvq-extension-swap", default-features = false }
pvq-extension = { path = "pvq-extension", default-features = false }
pvq-primitives = { path = "pvq-primitives", default-features = false }
pvq-runtime-api = { path = "pvq-runtime-api", default-features = false }
Expand Down
9 changes: 9 additions & 0 deletions guest-examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions guest-examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"total-supply",
"total-supply-hand-written",
"transparent-call-hand-written",
"test-swap-extension",
]
resolver = "2"

Expand Down
9 changes: 9 additions & 0 deletions guest-examples/test-swap-extension/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "guest-test-swap-extension"
version = "0.1.0"
edition = "2021"

[dependencies]
parity-scale-codec = { workspace = true }
polkavm-derive = { workspace = true }
pvq-program = { workspace = true }
59 changes: 59 additions & 0 deletions guest-examples/test-swap-extension/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#![no_std]
#![no_main]

#[pvq_program::program]
mod query_pools {

type AssetId = alloc::vec::Vec<u8>;
type Balance = u128;
const UNITS: Balance = 1_000_000_000_000;

#[program::extension_fn(extension_id = 13206387959972970661u64, fn_index = 0)]
fn quote_price_tokens_for_exact_tokens(
asset1: AssetId,
asset2: AssetId,
amount: Balance,
include_fee: bool,
) -> Option<Balance> {
}

#[program::extension_fn(extension_id = 13206387959972970661u64, fn_index = 1)]
fn quote_price_exact_tokens_for_tokens(
asset1: AssetId,
asset2: AssetId,
amount: Balance,
include_fee: bool,
) -> Option<Balance> {
}

#[program::extension_fn(extension_id = 13206387959972970661u64, fn_index = 2)]
fn get_liquidity_pool(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)> {}

#[program::extension_fn(extension_id = 13206387959972970661u64, fn_index = 3)]
fn list_pools() -> alloc::vec::Vec<(AssetId, AssetId, Balance, Balance)> {}

#[program::entrypoint]
fn test_swap_extension(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)> {
// Check quote prices
let amount_in = quote_price_tokens_for_exact_tokens(asset1.clone(), asset2.clone(), 10 * UNITS, false)
.expect("Quote price exists");

assert!(amount_in == 20 * UNITS);
let amount_out = quote_price_exact_tokens_for_tokens(asset1.clone(), asset2.clone(), 20 * UNITS, false)
.expect("Quote price exists");
assert!(amount_out == 10 * UNITS);

// // Check list_pools
let pools = list_pools();
assert!(pools.len() == 1);
let (pool_asset1, pool_asset2, pool_balance1, pool_balance2) = &pools[0];
assert!(pool_asset1 == &asset1);
assert!(pool_asset2 == &asset2);
assert!(*pool_balance1 == 100 * UNITS);
assert!(*pool_balance2 == 50 * UNITS);

// Check get_liquidity_pool
let (balance1, balance2) = get_liquidity_pool(asset1, asset2).expect("Pool exists");
Some((balance1, balance2))
}
}
1 change: 0 additions & 1 deletion pvq-executor/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ impl<Ctx: PvqExecutorContext> PvqExecutor<Ctx> {

let result = instance.read_memory(res_ptr, res_size)?;

tracing::info!("Result: {:?}", result);
Ok(result)
})();

Expand Down
17 changes: 17 additions & 0 deletions pvq-extension-swap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "pvq-extension-swap"
description = "Swap extension for PVQ"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true

[dependencies]
parity-scale-codec = { workspace = true }
pvq-extension = { workspace = true }
scale-info = { workspace = true }

[features]
default = ["std"]
std = ["parity-scale-codec/std", "scale-info/std", "pvq-extension/std"]
34 changes: 34 additions & 0 deletions pvq-extension-swap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;

use pvq_extension::extension_decl;

#[extension_decl]
pub mod extension {
use alloc::vec::Vec;

#[extension_decl::extension]
pub trait ExtensionSwap {
type AssetId;
type Balance;

fn quote_price_tokens_for_exact_tokens(
asset1: Self::AssetId,
asset2: Self::AssetId,
amount: Self::Balance,
include_fee: bool,
) -> Option<Self::Balance>;

fn quote_price_exact_tokens_for_tokens(
asset1: Self::AssetId,
asset2: Self::AssetId,
amount: Self::Balance,
include_fee: bool,
) -> Option<Self::Balance>;

fn get_liquidity_pool(asset1: Self::AssetId, asset2: Self::AssetId) -> Option<(Self::Balance, Self::Balance)>;

#[allow(clippy::type_complexity)]
fn list_pools() -> Vec<(Self::AssetId, Self::AssetId, Self::Balance, Self::Balance)>;
}
}
1 change: 1 addition & 0 deletions pvq-extension/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl<C: CallDataTuple, P: PermissionController> ExtensionsExecutor<C, P> {
/// The result of the execution or an error
pub fn execute(&mut self, program: &[u8], args: &[u8], gas_limit: Option<i64>) -> (PvqResult, Option<i64>) {
let (result, gas_remaining) = self.executor.execute(program, args, gas_limit);
tracing::info!("result: {:?}", result);
(result.map_err(PvqError::from), gas_remaining)
}
}
17 changes: 16 additions & 1 deletion pvq-extension/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,23 @@ pub trait CallDataTuple {
fn dispatch(extension_id: ExtensionIdTy, data: &[u8]) -> Result<Vec<u8>, ExtensionError>;
}

// Use the macro to implement ExtensionTuple for tuples of different lengths
impl<Member0> CallDataTuple for Member0
where
Member0: CallData,
{
fn dispatch(extension_id: ExtensionIdTy, mut data: &[u8]) -> Result<Vec<u8>, ExtensionError> {
if extension_id == Member0::EXTENSION_ID {
return Member0::decode(&mut data)
.map_err(ExtensionError::DecodeError)?
.dispatch()
.map_err(ExtensionError::DispatchError);
}
Err(ExtensionError::UnsupportedExtension)
}
}

fortuples! {
#[tuples::min_size(1)]
impl CallDataTuple for #Tuple where #(#Member: CallData),*{
#[allow(unused_variables)]
#[allow(unused_mut)]
Expand Down
1 change: 1 addition & 0 deletions pvq-test-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pvq-executor = { workspace = true, features = ["std"] }
pvq-extension = { workspace = true, features = ["std"] }
pvq-extension-core = { workspace = true, features = ["std"] }
pvq-extension-fungibles = { workspace = true, features = ["std"] }
pvq-extension-swap = { workspace = true, features = ["std"] }
pvq-primitives = { workspace = true, features = ["std"] }

polkavm = { workspace = true, features = ["std"] }
38 changes: 38 additions & 0 deletions pvq-test-runner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub enum ExtensionFungiblesFunctions {

#[extensions_impl]
pub mod extensions {
use parity_scale_codec::Decode;
#[extensions_impl::impl_struct]
pub struct ExtensionsImpl;

Expand All @@ -38,6 +39,38 @@ pub mod extensions {
100
}
}
#[extensions_impl::extension]
impl pvq_extension_swap::extension::ExtensionSwap for ExtensionsImpl {
type AssetId = Vec<u8>;
type Balance = u64;
fn quote_price_tokens_for_exact_tokens(
_asset1: Self::AssetId,
_asset2: Self::AssetId,
_amount: Self::Balance,
_include_fee: bool,
) -> Option<Self::Balance> {
None
}

fn quote_price_exact_tokens_for_tokens(
_asset1: Self::AssetId,
_asset2: Self::AssetId,
_amount: Self::Balance,
_include_fee: bool,
) -> Option<Self::Balance> {
None
}

fn get_liquidity_pool(asset1: Self::AssetId, asset2: Self::AssetId) -> Option<(Self::Balance, Self::Balance)> {
let _asset1 = u32::decode(&mut &asset1[..]).expect("Failed to decode asset1");
let _asset2 = u32::decode(&mut &asset2[..]).expect("Failed to decode asset2");
Some((100, 100))
}

fn list_pools() -> Vec<(Self::AssetId, Self::AssetId, Self::Balance, Self::Balance)> {
vec![]
}
}
}

pub struct TestRunner {
Expand Down Expand Up @@ -75,6 +108,11 @@ impl TestRunner {
}
.encode(),
);
} else if program_path.contains("liquidity-pool") {
let asset1 = u32::encode(&21);
let asset2 = u32::encode(&22);
input_data.extend_from_slice(&asset1.encode());
input_data.extend_from_slice(&asset2.encode());
}
tracing::info!("Input data (hex): {}", HexDisplay::from(&input_data));
input_data
Expand Down
Loading