Skip to content
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
8 changes: 4 additions & 4 deletions .github/workflows/rust.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ on:
workflow_dispatch:

env:
RUSTVERS: "1.90.0" # only applies to non solana platform tools rustc
SOLANAVERS: "2.2.20"
# all the stuff that usually gets cached to ~
# now gets cached to repo dir so that cache action can pick it up
HOME: ${{ github.workspace }}
CARGO_HOME: ${{ github.workspace }}/.cargo
RUSTVERS: "1.90.0" # only applies to non solana platform tools rustc
SOLANAVERS: "2.2.20"

jobs:
# hax from https://github.com/orgs/community/discussions/26324#discussioncomment-3251460
Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
path: |
${{ env.CARGO_HOME }}
target
key: ${{ runner.os }}-rustlint-${{ hashFiles('**/Cargo.lock') }}
key: ${{ runner.os }}-rustlint-${{ env.RUSTVERS }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-rustlint-
- name: Clippy
Expand Down Expand Up @@ -98,7 +98,7 @@ jobs:
path: |
${{ env.CARGO_HOME }}
target
key: ${{ runner.os }}-sbf-${{ hashFiles('**/Cargo.lock') }}
key: ${{ runner.os }}-sbf-${{ env.SOLANAVERS }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-sbf-
- name: Build
Expand Down
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ version = "0.1.0"
# In general, keep default-features = false in workspace
# and enable individual features in indiv crates
anyhow = { version = "^1", default-features = false }
# TODO: migrate to five8
bs58-fixed = { git = "https://github.com/igneous-labs/bs58-fixed.git", branch = "master", default-features = false }
bs58-fixed-wasm = { git = "https://github.com/igneous-labs/bs58-fixed.git", branch = "master", default-features = false }
const-crypto = { version = "^0.3.0", default-features = false }
Expand All @@ -46,8 +47,8 @@ generic-array-struct = { version = "^0.3.1", default-features = false }
hmac-sha256 = { version = "^1", default-features = false }
jupiter-amm-interface = { version = "^0.6", default-features = false }
rust_decimal = { version = "^1.36.0", default-features = false } # vers constraint based on jupiter-amm-interface
sanctum-fee-ratio = { version = "^2", default-features = false }
sanctum-u64-ratio = { version = "^2", default-features = false }
sanctum-fee-ratio = { version = "^2.1", default-features = false }
sanctum-u64-ratio = { version = "^2.1", default-features = false }
serde = { version = "^1", default-features = false }
serde_bytes = { version = "^0.11", default-features = false }
tsify-next = { version = "^0.5.5", default-features = false }
Expand All @@ -63,6 +64,9 @@ rand = { version = "^0.9", default-features = false }
serde_json = { version = "^1", default-features = false }

# solana crates (for dev)
# Make sure lowest vers number of solana-* subdeps matches
# solana toolchain vers in README bec solana-verify
# seems to select toolchain vers based on that
mollusk-svm = { version = "^0.5", default-features = false }
mollusk-svm-programs-token = { version = "^0.5", default-features = false }
solana-account = { version = "^2.2.1", default-features = false } # vers constraint based on solana-sdk subdep
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ solana-cargo-build-sbf 2.2.20
platform-tools v1.48
rustc 1.84.1
```

Install with

```sh
sh -c "$(curl -sSfL https://release.anza.xyz/v2.2.20/install)"
```
4 changes: 2 additions & 2 deletions jup-interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license-file.workspace = true
version.workspace = true
include = ["src/**/*", "Cargo.toml"]

[dependencies]
[target.'cfg(not(target_os = "solana"))'.dependencies]
anyhow = { workspace = true }
inf1-std = { workspace = true }
jupiter-amm-interface = { workspace = true }
Expand All @@ -18,7 +18,7 @@ solana-instruction = { workspace = true }
solana-pubkey = { workspace = true }
solana-sha256-hasher = { workspace = true }

[dev-dependencies]
[target.'cfg(not(target_os = "solana"))'.dev-dependencies]
generic-array-struct = { workspace = true }
inf1-test-utils = { workspace = true }
lazy_static = { workspace = true }
Expand Down
3 changes: 3 additions & 0 deletions jup-interface/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#![allow(unexpected_cfgs)]
#![cfg(not(target_os = "solana"))]

use std::{
iter::once,
sync::{
Expand Down
87 changes: 71 additions & 16 deletions pricing/flatslab/core/src/pricing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ pub struct FlatSlabSwapPricing {
pub out_fee_nanos: FeeNanos,
}

/// Checks
impl FlatSlabSwapPricing {
#[inline]
pub const fn is_net_negative(&self) -> bool {
// unchecked-arith: FeeNanos valid range will not overflow
self.inp_fee_nanos.get() + self.out_fee_nanos.get() < 0
}
}

/// Pricing
impl FlatSlabSwapPricing {
/// Returns the ratio that returns out_sol_value
/// when applied to in_sol_value
Expand All @@ -47,7 +57,8 @@ impl FlatSlabSwapPricing {
// post_fee_nanos = 1_000_000_000 - fee_nanos
// out_sol_value = floor(in_sol_value * post_fee_nanos / 1_000_000_000)
// i32 signed subtraction:
// - rebates are allowed (post_fee_nanos > 1_000_000_000)
// - rebates are allowed here (post_fee_nanos > 1_000_000_000)
// but should've been filtered out by `self.is_net_negative()` check
// - however, >100% fees will error (post_fee_nanos < 0)
let post_fee_nanos = match NANOS_DENOM.checked_sub(fee_nanos) {
None => return None,
Expand All @@ -65,38 +76,50 @@ impl FlatSlabSwapPricing {
}

#[inline]
pub const fn pp_price_exact_in(&self, in_sol_value: u64) -> Option<u64> {
match self.out_ratio() {
None => None,
Some(r) => r.apply(in_sol_value),
pub const fn pp_price_exact_in(&self, in_sol_value: u64) -> Result<u64, FlatSlabPricingErr> {
if self.is_net_negative() {
return Err(FlatSlabPricingErr::NetNegativeFees);
}
let r = match self.out_ratio() {
None => return Err(FlatSlabPricingErr::Ratio),
Some(r) => r,
};
match r.apply(in_sol_value) {
None => Err(FlatSlabPricingErr::Ratio),
Some(x) => Ok(x),
}
}

#[inline]
pub const fn pp_price_exact_out(&self, out_sol_value: u64) -> Option<u64> {
pub const fn pp_price_exact_out(&self, out_sol_value: u64) -> Result<u64, FlatSlabPricingErr> {
if self.is_net_negative() {
return Err(FlatSlabPricingErr::NetNegativeFees);
}
// the greatest possible non-u64::MAX value of in_sol_value is 1_000_000_00 x out_sol_value.
// Otherwise if fee is 100% then this will return None unless out_sol_value == 0
let range_opt = match self.out_ratio() {
None => return None,
Some(r) => r.reverse(out_sol_value),
let r = match self.out_ratio() {
None => return Err(FlatSlabPricingErr::Ratio),
Some(r) => r,
};
let range = match range_opt {
None => return None,
let range = match r.reverse(out_sol_value) {
None => return Err(FlatSlabPricingErr::Ratio),
Some(r) => r,
};
Some(*range.end())
Ok(*range.end())
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FlatSlabPricingErr {
NetNegativeFees,
Ratio,
}

impl Display for FlatSlabPricingErr {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::NetNegativeFees => "net negative fees disallowed",
Self::Ratio => "ratio math error",
})
}
Expand All @@ -113,7 +136,6 @@ impl PriceExactIn for FlatSlabSwapPricing {
PriceExactInIxArgs { sol_value, .. }: PriceExactInIxArgs,
) -> Result<u64, Self::Error> {
self.pp_price_exact_in(sol_value)
.ok_or(FlatSlabPricingErr::Ratio)
}
}

Expand All @@ -126,7 +148,6 @@ impl PriceExactOut for FlatSlabSwapPricing {
PriceExactOutIxArgs { sol_value, .. }: PriceExactOutIxArgs,
) -> Result<u64, Self::Error> {
self.pp_price_exact_out(sol_value)
.ok_or(FlatSlabPricingErr::Ratio)
}
}

Expand All @@ -140,7 +161,6 @@ impl PriceLpTokensToRedeem for FlatSlabSwapPricing {
PriceLpTokensToRedeemIxArgs { sol_value, .. }: PriceLpTokensToRedeemIxArgs,
) -> Result<u64, Self::Error> {
self.pp_price_exact_in(sol_value)
.ok_or(FlatSlabPricingErr::Ratio)
}
}

Expand All @@ -153,7 +173,6 @@ impl PriceLpTokensToMint for FlatSlabSwapPricing {
PriceLpTokensToMintIxArgs { sol_value, .. }: PriceLpTokensToMintIxArgs,
) -> Result<u64, Self::Error> {
self.pp_price_exact_in(sol_value)
.ok_or(FlatSlabPricingErr::Ratio)
}
}

Expand Down Expand Up @@ -293,6 +312,19 @@ mod tests {
}
}

prop_compose! {
/// inp out nanos pair that will result in a fee rate < 0
fn net_neg_fee()
(i in *FeeNanos::MIN..=-*FeeNanos::MIN) // since abs(MIN) < abs(MAX)
(i in Just(i), o in *FeeNanos::MIN..-i)
-> FlatSlabSwapPricing {
FlatSlabSwapPricing {
inp_fee_nanos: FeeNanos::new(i).unwrap(),
out_fee_nanos: FeeNanos::new(o).unwrap()
}
}
}

// General

proptest! {
Expand Down Expand Up @@ -546,4 +578,27 @@ mod tests {
prop_assert_eq!(mint_sol_value, sol_value);
}
}

proptest! {
#[allow(deprecated)]
#[test]
fn net_neg_fee_errs(
fee in net_neg_fee(),
sol_value: u64,
amt: u64, // dont-care
) {
let args = PriceExactInIxArgs { sol_value, amt };
for f in [
FlatSlabSwapPricing::price_exact_in,
FlatSlabSwapPricing::price_exact_out,
FlatSlabSwapPricing::price_lp_tokens_to_redeem,
FlatSlabSwapPricing::price_lp_tokens_to_mint,
] {
prop_assert_eq!(
f(&fee, args),
Err(FlatSlabPricingErr::NetNegativeFees),
);
}
}
}
}
5 changes: 3 additions & 2 deletions pricing/flatslab/program/src/err.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! TODO: this should maybe be in its own `inf1-pp-flatslab-jiminy` crate

use inf1_pp_flatslab_core::errs::FlatSlabProgramErr;
use inf1_pp_flatslab_core::{errs::FlatSlabProgramErr, pricing::FlatSlabPricingErr};
use jiminy_entrypoint::program_error::ProgramError;

/// Example-usage:
Expand Down Expand Up @@ -64,7 +64,8 @@ seqerr!(
FeeNanosOutOfRange(_),
MintNotFound(_),
MissingAdminSignature,
Pricing(_),
Pricing(FlatSlabPricingErr::NetNegativeFees),
Pricing(FlatSlabPricingErr::Ratio),
WrongSlabAcc
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ pub fn process_price_exact_in(
},
)?
.price_exact_in(args)
.map_err(|e| CustomProgErr(FlatSlabProgramErr::Pricing(e)))?;
.map_err(FlatSlabProgramErr::Pricing)
.map_err(CustomProgErr)?;
set_return_data(&ret.to_le_bytes());
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ pub fn process_price_exact_out(
},
)?
.price_exact_out(args)
.map_err(|e| CustomProgErr(FlatSlabProgramErr::Pricing(e)))?;
.map_err(FlatSlabProgramErr::Pricing)
.map_err(CustomProgErr)?;
set_return_data(&ret.to_le_bytes());
Ok(())
}
6 changes: 3 additions & 3 deletions pricing/flatslab/program/src/instructions/pricing/mint_lp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ pub fn process_price_lp_tokens_to_mint(
let ret = slab
.entries()
.pricing(&pair)
.map_err(|e| CustomProgErr(FlatSlabProgramErr::MintNotFound(e)))?
.price_exact_in(args)
.map_err(|e| CustomProgErr(FlatSlabProgramErr::Pricing(e)))?;
.map_err(FlatSlabProgramErr::MintNotFound)
.and_then(|p| p.price_exact_in(args).map_err(FlatSlabProgramErr::Pricing))
.map_err(CustomProgErr)?;
set_return_data(&ret.to_le_bytes());
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ pub fn process_price_lp_tokens_to_redeem(
let ret = slab
.entries()
.pricing(&pair)
.map_err(|e| CustomProgErr(FlatSlabProgramErr::MintNotFound(e)))?
.price_exact_in(args)
.map_err(|e| CustomProgErr(FlatSlabProgramErr::Pricing(e)))?;
.map_err(FlatSlabProgramErr::MintNotFound)
.and_then(|p| p.price_exact_in(args).map_err(FlatSlabProgramErr::Pricing))
.map_err(CustomProgErr)?;
set_return_data(&ret.to_le_bytes());
Ok(())
}
5 changes: 5 additions & 0 deletions pricing/flatslab/program/tests/common/props.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! FIXME: refactor tests:
//! - move generation to test-utils crate
//! - change generation from "account to args" style to
//! "args to account" style, like everything else in test-utils gen/ folder

use std::ops::RangeInclusive;

use inf1_pp_core::pair::Pair;
Expand Down
9 changes: 8 additions & 1 deletion pricing/flatslab/program/tests/tests/pricing/exact_in.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,23 @@ proptest! {
&accs.seq().cloned().collect::<Vec<_>>(),
);
let lib_res = pricing.price_exact_in(args);

match (program_result, lib_res) {
(ProgramResult::Success, Ok(lib_res)) => {
prop_assert_eq!(lib_res, u64::from_le_bytes(return_data.try_into().unwrap()));
}
(ProgramResult::Failure(e), Err(_)) => {
(ProgramResult::Failure(e), Err(FlatSlabPricingErr::Ratio)) => {
assert_prog_err_eq(
&e,
&ProgramError::from(CustomProgErr(FlatSlabProgramErr::Pricing(FlatSlabPricingErr::Ratio)))
);
},
(ProgramResult::Failure(e), Err(FlatSlabPricingErr::NetNegativeFees)) => {
assert_prog_err_eq(
&e,
&ProgramError::from(CustomProgErr(FlatSlabProgramErr::Pricing(FlatSlabPricingErr::NetNegativeFees)))
);
},
(a, b) => {
panic!("{a:#?}, {b:#?}");
}
Expand Down
9 changes: 8 additions & 1 deletion pricing/flatslab/program/tests/tests/pricing/exact_out.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,23 @@ proptest! {
&accs.seq().cloned().collect::<Vec<_>>(),
);
let lib_res = pricing.price_exact_out(args);

match (program_result, lib_res) {
(ProgramResult::Success, Ok(lib_res)) => {
prop_assert_eq!(lib_res, u64::from_le_bytes(return_data.try_into().unwrap()));
}
(ProgramResult::Failure(e), Err(_)) => {
(ProgramResult::Failure(e), Err(FlatSlabPricingErr::Ratio)) => {
assert_prog_err_eq(
&e,
&ProgramError::from(CustomProgErr(FlatSlabProgramErr::Pricing(FlatSlabPricingErr::Ratio)))
);
},
(ProgramResult::Failure(e), Err(FlatSlabPricingErr::NetNegativeFees)) => {
assert_prog_err_eq(
&e,
&ProgramError::from(CustomProgErr(FlatSlabProgramErr::Pricing(FlatSlabPricingErr::NetNegativeFees)))
);
},
(a, b) => {
panic!("{a:#?}, {b:#?}");
}
Expand Down
2 changes: 1 addition & 1 deletion test-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license-file.workspace = true
version.workspace = true
publish = false

[dependencies]
[target.'cfg(not(target_os = "solana"))'.dependencies]
generic-array-struct = { workspace = true }
glob = { workspace = true }
inf1-ctl-core = { workspace = true }
Expand Down
Loading