Skip to content
Open
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
12 changes: 6 additions & 6 deletions 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 integration-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod stableswap;
mod stableswap_curve_comparison;
mod staking;
mod transact_call_filter;
mod uniswap_v3_router;
mod utility;
pub mod utils;
mod vesting;
Expand Down
130 changes: 130 additions & 0 deletions integration-tests/src/uniswap_v3_router.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#![cfg(test)]

use crate::polkadot_test_net::*;
use frame_support::assert_ok;
use hex_literal::hex;
use hydradx_runtime::evm::uniswap_v3_trade_executor::UniswapV3;
use hydradx_runtime::{AssetId, Currencies, Parameters, Router, Runtime, RuntimeEvent, RuntimeOrigin};
use hydradx_traits::router::{PoolType, Trade};
use orml_traits::MultiCurrency;
use pallet_broadcast::types::Filler;
use pallet_route_executor::TradeExecution;
use primitives::{Balance, EvmAddress};
use sp_core::H160;

pub const PATH_TO_SNAPSHOT: &str = "uniswap-snapshot/SNAPSHOT";

const UNISWAP_V3_FACTORY: EvmAddress = H160(hex!("0000000000000000000000000000000000000000"));
const UNISWAP_V3_SWAP_ROUTER: EvmAddress = H160(hex!("0000000000000000000000000000000000000000"));
const UNISWAP_V3_QUOTER: EvmAddress = H160(hex!("0000000000000000000000000000000000000000"));

const ASSET_IN: AssetId = 0;
const ASSET_OUT: AssetId = 20;
const FEE_TIER: u32 = 3000;
const SELL_AMOUNT: Balance = 1_000_000_000_000;

fn with_uniswap_v3(execution: impl FnOnce()) {
TestNet::reset();
hydra_live_ext(PATH_TO_SNAPSHOT).execute_with(|| {
assert_ok!(Parameters::set_uniswap_v3_addresses(
RuntimeOrigin::root(),
UNISWAP_V3_FACTORY,
UNISWAP_V3_SWAP_ROUTER,
UNISWAP_V3_QUOTER,
));
execution();
});
}

fn uniswap_route() -> Vec<Trade<AssetId>> {
vec![Trade {
pool: PoolType::UniswapV3(FEE_TIER),
asset_in: ASSET_IN,
asset_out: ASSET_OUT,
}]
}

#[test]
#[ignore = "requires uniswap-snapshot/SNAPSHOT and deployment constants; see uniswap-snapshot/README.md"]
fn calculate_out_given_in_should_return_positive_quote_when_pool_has_liquidity() {
with_uniswap_v3(|| {
let amount_out =
UniswapV3::calculate_out_given_in(PoolType::UniswapV3(FEE_TIER), ASSET_IN, ASSET_OUT, SELL_AMOUNT)
.expect("quote should succeed");
assert!(amount_out > 0);
});
}

#[test]
#[ignore = "requires uniswap-snapshot/SNAPSHOT and deployment constants; see uniswap-snapshot/README.md"]
fn calculate_in_given_out_should_exceed_output_when_pool_charges_fee() {
with_uniswap_v3(|| {
let amount_out = SELL_AMOUNT / 2;
let amount_in =
UniswapV3::calculate_in_given_out(PoolType::UniswapV3(FEE_TIER), ASSET_IN, ASSET_OUT, amount_out)
.expect("quote should succeed");
assert!(amount_in > amount_out);
});
}

#[test]
#[ignore = "requires uniswap-snapshot/SNAPSHOT and deployment constants; see uniswap-snapshot/README.md"]
fn router_sell_should_increase_output_balance_when_routed_through_uniswap_v3() {
with_uniswap_v3(|| {
let before = Currencies::free_balance(ASSET_OUT, &ALICE.into());
assert_ok!(Router::sell(
RuntimeOrigin::signed(ALICE.into()),
ASSET_IN,
ASSET_OUT,
SELL_AMOUNT,
0,
uniswap_route().try_into().unwrap(),
));
let after = Currencies::free_balance(ASSET_OUT, &ALICE.into());
assert!(after > before);
});
}

#[test]
#[ignore = "requires uniswap-snapshot/SNAPSHOT and deployment constants; see uniswap-snapshot/README.md"]
fn router_buy_should_deliver_exact_output_when_routed_through_uniswap_v3() {
with_uniswap_v3(|| {
let buy_amount = SELL_AMOUNT / 2;
let before = Currencies::free_balance(ASSET_OUT, &ALICE.into());
assert_ok!(Router::buy(
RuntimeOrigin::signed(ALICE.into()),
ASSET_IN,
ASSET_OUT,
buy_amount,
u128::MAX,
uniswap_route().try_into().unwrap(),
));
let after = Currencies::free_balance(ASSET_OUT, &ALICE.into());
assert_eq!(after - before, buy_amount);
});
}

#[test]
#[ignore = "requires uniswap-snapshot/SNAPSHOT and deployment constants; see uniswap-snapshot/README.md"]
fn router_sell_should_emit_uniswap_v3_filler_event() {
with_uniswap_v3(|| {
assert_ok!(Router::sell(
RuntimeOrigin::signed(ALICE.into()),
ASSET_IN,
ASSET_OUT,
SELL_AMOUNT,
0,
uniswap_route().try_into().unwrap(),
));
let emitted = frame_system::Pallet::<Runtime>::events().into_iter().any(|record| {
matches!(
record.event,
RuntimeEvent::Broadcast(pallet_broadcast::Event::Swapped3 {
filler_type: Filler::UniswapV3,
..
})
)
});
assert!(emitted);
});
}
68 changes: 68 additions & 0 deletions integration-tests/uniswap-snapshot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# uniswap-snapshot

Snapshot-backed integration tests for the Uniswap v3 router venue
(`src/uniswap_v3_router.rs`). The tests load a scraped EVM state snapshot that
already contains a deployed Uniswap v3 stack (Factory, SwapRouter02, QuoterV2,
a pool with liquidity), then drive the runtime router against it — the same
pattern as `aave_router.rs` / `evm-snapshot/`.

The tests are `#[ignore]`d until the `SNAPSHOT` artifact and the deployment
constants are committed (the binary is too large to ship by default, and the
addresses depend on the deployment used). Follow the steps below to produce them.

## 1. Bring up a chain with Uniswap v3 deployed

Use the local zombienet + Uniswap v3 deploy from the `ys-` branches (the
`uniswap-v3-deploy` sibling repo). The chain must run **this** runtime
(`feat/uniswap-v3-router`) so the snapshot is loadable, and must have:

- Factory, SwapRouter02, QuoterV2 deployed.
- At least one pool created **and initialized** for an asset pair that maps to
two registered Hydration assets, with liquidity minted (via the
NonfungiblePositionManager) so quotes and swaps return non-zero.
- `ALICE` funded with `ASSET_IN`.

Record the deployed addresses (the deploy writes them to
`uniswap-v3-deploy/deployments/<network>/_addresses.json`).

## 2. Build the scraper

```bash
cargo build --release -p scraper
```

## 3. Scrape the EVM + registry + balances into a snapshot

```bash
./target/release/scraper save-storage \
--pallet EVM AssetRegistry System Tokens Omnipool Timestamp Parameters \
--uri ws://127.0.0.1:9988 \
--path integration-tests/uniswap-snapshot
```

This writes `integration-tests/uniswap-snapshot/SNAPSHOT_<block>`. Rename it to
`SNAPSHOT` (or update `PATH_TO_SNAPSHOT` in `src/uniswap_v3_router.rs`). Add
`--slim` to drop most user accounts and keep the file small.

> Including the `Parameters` pallet means that if you call
> `parameters.setUniswapV3Addresses` on the source chain **before** scraping,
> the addresses are baked into the snapshot and `with_uniswap_v3` doesn't need
> to set them — in that case the `UNISWAP_V3_*` constants below are only used as
> a fallback.

## 4. Fill in the deployment constants

In `src/uniswap_v3_router.rs` set, from the deploy's `_addresses.json` and your
asset registry:

- `UNISWAP_V3_FACTORY`, `UNISWAP_V3_SWAP_ROUTER`, `UNISWAP_V3_QUOTER`
- `ASSET_IN`, `ASSET_OUT`, `FEE_TIER` — the pair + fee tier of the seeded pool
- `SELL_AMOUNT` — comfortably below the pool's depth

## 5. Run the tests

```bash
cargo test -p runtime-integration-tests uniswap_v3_router -- --ignored
```

Drop the `#[ignore]` attributes once `SNAPSHOT` and the constants are committed.
2 changes: 1 addition & 1 deletion pallets/broadcast/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pallet-broadcast"
version = "1.7.0"
version = "1.8.0"
authors = ["GalacticCouncil"]
edition = "2021"
license = "Apache 2.0"
Expand Down
1 change: 1 addition & 0 deletions pallets/broadcast/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum Filler {
OTC(OtcOrderId),
AAVE, // ICE(solution_id/block id), swapper: alice, filler: solver
HSM,
UniswapV3,
}

#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Copy, Debug, Eq, PartialEq, TypeInfo, MaxEncodedLen)]
Expand Down
2 changes: 1 addition & 1 deletion pallets/parameters/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pallet-parameters"
version = "1.2.0"
version = "1.3.0"
authors = ['GalacticCouncil']
edition = "2021"
license = "Apache-2.0"
Expand Down
32 changes: 32 additions & 0 deletions pallets/parameters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ mod tests;
pub use pallet::*;

use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::OriginFor;
use sp_core::H160;

#[frame_support::pallet]
pub mod pallet {
Expand All @@ -56,6 +58,18 @@ pub mod pallet {
#[pallet::getter(fn relay_parent_offset_override)]
pub type RelayParentOffsetOverride<T> = StorageValue<_, bool, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn uniswap_v3_factory)]
pub type UniswapV3Factory<T> = StorageValue<_, H160, OptionQuery>;

#[pallet::storage]
#[pallet::getter(fn uniswap_v3_swap_router)]
pub type UniswapV3SwapRouter<T> = StorageValue<_, H160, OptionQuery>;

#[pallet::storage]
#[pallet::getter(fn uniswap_v3_quoter)]
pub type UniswapV3Quoter<T> = StorageValue<_, H160, OptionQuery>;

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub is_testnet: bool,
Expand All @@ -81,6 +95,24 @@ pub mod pallet {
}
}

#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::DbWeight::get().writes(3))]
pub fn set_uniswap_v3_addresses(
origin: OriginFor<T>,
factory: H160,
swap_router: H160,
quoter: H160,
) -> DispatchResult {
frame_system::ensure_root(origin)?;
UniswapV3Factory::<T>::put(factory);
UniswapV3SwapRouter::<T>::put(swap_router);
UniswapV3Quoter::<T>::put(quoter);
Ok(())
}
}

impl<T: Config> Pallet<T> {
/// Set the flag. Only used for tests.
#[cfg(feature = "std")]
Expand Down
2 changes: 1 addition & 1 deletion runtime/hydradx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hydradx-runtime"
version = "428.0.0"
version = "429.0.0"
authors = ["GalacticCouncil"]
edition = "2021"
license = "Apache 2.0"
Expand Down
Loading
Loading