Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit d62ddd2

Browse files
authored
Added stable curve invariant to the token swap smart contract (#838)
* Added stable curve invariant to the token swap smart contract * Fixed formatting * Added missing stable curve constraints * Symbol renames to make math clearer * Small refactoring according to PR comments, fixes for JS tests
1 parent c18ea44 commit d62ddd2

File tree

11 files changed

+594
-66
lines changed

11 files changed

+594
-66
lines changed

token-swap/js/client/token-swap.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struc
7676
Layout.uint64('ownerWithdrawFeeDenominator'),
7777
Layout.uint64('hostFeeNumerator'),
7878
Layout.uint64('hostFeeDenominator'),
79+
Layout.uint64('amp'),
7980
],
8081
);
8182

@@ -310,6 +311,7 @@ export class TokenSwap {
310311
BufferLayout.nu64('ownerWithdrawFeeDenominator'),
311312
BufferLayout.nu64('hostFeeNumerator'),
312313
BufferLayout.nu64('hostFeeDenominator'),
314+
BufferLayout.nu64('amp'),
313315
]);
314316
let data = Buffer.alloc(1024);
315317
{

token-swap/program/src/constraints.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::{
44
curve::base::{CurveType, SwapCurve},
5-
curve::{constant_product::ConstantProductCurve, flat::FlatCurve},
5+
curve::{constant_product::ConstantProductCurve, flat::FlatCurve, stable::StableCurve},
66
error::SwapError,
77
};
88

@@ -24,6 +24,8 @@ pub struct FeeConstraints<'a> {
2424
pub valid_flat_curves: &'a [FlatCurve],
2525
/// Valid constant product curves for the program
2626
pub valid_constant_product_curves: &'a [ConstantProductCurve],
27+
/// Valid stable curves for the program
28+
pub valid_stable_curves: &'a [StableCurve],
2729
}
2830

2931
impl<'a> FeeConstraints<'a> {
@@ -46,6 +48,14 @@ impl<'a> FeeConstraints<'a> {
4648
calculator: Box::new(x.clone()),
4749
})
4850
.collect(),
51+
CurveType::Stable => self
52+
.valid_stable_curves
53+
.iter()
54+
.map(|x| SwapCurve {
55+
curve_type: swap_curve.curve_type,
56+
calculator: Box::new(x.clone()),
57+
})
58+
.collect(),
4959
};
5060
if valid_swap_curves.iter().any(|x| *x == *swap_curve) {
5161
Ok(())
@@ -79,6 +89,8 @@ const VALID_FLAT_CURVES: &[FlatCurve] = &[FlatCurve {
7989
host_fee_numerator: 20,
8090
host_fee_denominator: 100,
8191
}];
92+
#[cfg(feature = "production")]
93+
const VALID_STABLE_CURVES: &[StableCurve] = &[];
8294

8395
/// Fee structure defined by program creator in order to enforce certain
8496
/// fees when others use the program. Adds checks on pool creation and
@@ -90,6 +102,7 @@ pub const FEE_CONSTRAINTS: Option<FeeConstraints> = {
90102
owner_key: OWNER_KEY,
91103
valid_constant_product_curves: VALID_CONSTANT_PRODUCT_CURVES,
92104
valid_flat_curves: VALID_FLAT_CURVES,
105+
valid_stable_curves: VALID_STABLE_CURVES,
93106
})
94107
}
95108
#[cfg(not(feature = "production"))]
@@ -134,6 +147,7 @@ mod tests {
134147
owner_key,
135148
valid_constant_product_curves: &[calculator.clone()],
136149
valid_flat_curves: &[],
150+
valid_stable_curves: &[],
137151
};
138152

139153
fee_constraints.validate_curve(&swap_curve).unwrap();

token-swap/program/src/curve/base.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use solana_program::{
77

88
use crate::curve::{
99
calculator::CurveCalculator, constant_product::ConstantProductCurve, flat::FlatCurve,
10+
stable::StableCurve,
1011
};
1112
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
1213
use std::convert::{TryFrom, TryInto};
@@ -20,6 +21,8 @@ pub enum CurveType {
2021
ConstantProduct,
2122
/// Flat line, always providing 1:1 from one token to another
2223
Flat,
24+
/// Stable, Like uniswap, but with wide zone of 1:1 instead of one point
25+
Stable,
2326
}
2427

2528
/// Concrete struct to wrap around the trait object which performs calculation.
@@ -75,15 +78,15 @@ impl Sealed for SwapCurve {}
7578
impl Pack for SwapCurve {
7679
/// Size of encoding of all curve parameters, which include fees and any other
7780
/// constants used to calculate swaps, deposits, and withdrawals.
78-
/// This includes 1 byte for the type, and 64 for the calculator to use as
79-
/// it needs. Some calculators may be smaller than 64 bytes.
80-
const LEN: usize = 65;
81+
/// This includes 1 byte for the type, and 72 for the calculator to use as
82+
/// it needs. Some calculators may be smaller than 72 bytes.
83+
const LEN: usize = 73;
8184

8285
/// Unpacks a byte buffer into a SwapCurve
8386
fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
84-
let input = array_ref![input, 0, 65];
87+
let input = array_ref![input, 0, 73];
8588
#[allow(clippy::ptr_offset_with_cast)]
86-
let (curve_type, calculator) = array_refs![input, 1, 64];
89+
let (curve_type, calculator) = array_refs![input, 1, 72];
8790
let curve_type = curve_type[0].try_into()?;
8891
Ok(Self {
8992
curve_type,
@@ -92,14 +95,15 @@ impl Pack for SwapCurve {
9295
Box::new(ConstantProductCurve::unpack_from_slice(calculator)?)
9396
}
9497
CurveType::Flat => Box::new(FlatCurve::unpack_from_slice(calculator)?),
98+
CurveType::Stable => Box::new(StableCurve::unpack_from_slice(calculator)?),
9599
},
96100
})
97101
}
98102

99103
/// Pack SwapCurve into a byte buffer
100104
fn pack_into_slice(&self, output: &mut [u8]) {
101-
let output = array_mut_ref![output, 0, 65];
102-
let (curve_type, calculator) = mut_array_refs![output, 1, 64];
105+
let output = array_mut_ref![output, 0, 73];
106+
let (curve_type, calculator) = mut_array_refs![output, 1, 72];
103107
curve_type[0] = self.curve_type as u8;
104108
self.calculator.pack_into_slice(&mut calculator[..]);
105109
}
@@ -120,6 +124,7 @@ impl TryFrom<u8> for CurveType {
120124
match curve_type {
121125
0 => Ok(CurveType::ConstantProduct),
122126
1 => Ok(CurveType::Flat),
127+
2 => Ok(CurveType::Stable),
123128
_ => Err(ProgramError::InvalidAccountData),
124129
}
125130
}
@@ -170,6 +175,7 @@ mod tests {
170175
packed.extend_from_slice(&owner_withdraw_fee_denominator.to_le_bytes());
171176
packed.extend_from_slice(&host_fee_numerator.to_le_bytes());
172177
packed.extend_from_slice(&host_fee_denominator.to_le_bytes());
178+
packed.extend_from_slice(&0u64.to_le_bytes()); // 8 bytes reserved for larget calcs
173179
let unpacked = SwapCurve::unpack_from_slice(&packed).unwrap();
174180
assert_eq!(swap_curve, unpacked);
175181
}

token-swap/program/src/curve/calculator.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ pub trait CurveCalculator: Debug + DynPack {
8181
Some(0)
8282
}
8383

84+
/// Calculate the trading fee in trading tokens
85+
/// Default implementation assumes no fee
86+
fn owner_trading_fee(&self, _trading_tokens: u128) -> Option<u128> {
87+
Some(0)
88+
}
89+
8490
/// Calculate the pool token equivalent of the owner fee on trade
8591
/// See the math at: https://balancer.finance/whitepaper/#single-asset-deposit
8692
/// For the moment, we do an approximation for the square root. For numbers

token-swap/program/src/curve/constant_product.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ impl CurveCalculator for ConstantProductCurve {
4242
) -> Option<SwapResult> {
4343
// debit the fee to calculate the amount swapped
4444
let trade_fee = self.trading_fee(source_amount)?;
45-
let owner_fee = calculate_fee(
46-
source_amount,
47-
u128::try_from(self.owner_trade_fee_numerator).ok()?,
48-
u128::try_from(self.owner_trade_fee_denominator).ok()?,
49-
)?;
45+
let owner_fee = self.owner_trading_fee(source_amount)?;
5046

5147
let invariant = swap_source_amount.checked_mul(swap_destination_amount)?;
5248
let new_source_amount_less_fee = swap_source_amount
@@ -86,6 +82,15 @@ impl CurveCalculator for ConstantProductCurve {
8682
)
8783
}
8884

85+
/// Calculate the owner trading fee in trading tokens
86+
fn owner_trading_fee(&self, trading_tokens: u128) -> Option<u128> {
87+
calculate_fee(
88+
trading_tokens,
89+
u128::try_from(self.owner_trade_fee_numerator).ok()?,
90+
u128::try_from(self.owner_trade_fee_denominator).ok()?,
91+
)
92+
}
93+
8994
/// Calculate the host fee based on the owner fee, only used in production
9095
/// situations where a program is hosted by multiple frontends
9196
fn host_fee(&self, owner_fee: u128) -> Option<u128> {
@@ -106,6 +111,10 @@ impl IsInitialized for ConstantProductCurve {
106111
impl Sealed for ConstantProductCurve {}
107112
impl Pack for ConstantProductCurve {
108113
const LEN: usize = 64;
114+
fn pack_into_slice(&self, output: &mut [u8]) {
115+
(self as &dyn DynPack).pack_into_slice(output);
116+
}
117+
109118
fn unpack_from_slice(input: &[u8]) -> Result<ConstantProductCurve, ProgramError> {
110119
let input = array_ref![input, 0, 64];
111120
#[allow(clippy::ptr_offset_with_cast)]
@@ -130,10 +139,6 @@ impl Pack for ConstantProductCurve {
130139
host_fee_denominator: u64::from_le_bytes(*host_fee_denominator),
131140
})
132141
}
133-
134-
fn pack_into_slice(&self, output: &mut [u8]) {
135-
(self as &dyn DynPack).pack_into_slice(output);
136-
}
137142
}
138143

139144
impl DynPack for ConstantProductCurve {

token-swap/program/src/curve/flat.rs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,8 @@ impl CurveCalculator for FlatCurve {
3939
swap_destination_amount: u128,
4040
) -> Option<SwapResult> {
4141
// debit the fee to calculate the amount swapped
42-
let trade_fee = calculate_fee(
43-
source_amount,
44-
u128::try_from(self.trade_fee_numerator).ok()?,
45-
u128::try_from(self.trade_fee_denominator).ok()?,
46-
)?;
47-
let owner_fee = calculate_fee(
48-
source_amount,
49-
u128::try_from(self.owner_trade_fee_numerator).ok()?,
50-
u128::try_from(self.owner_trade_fee_denominator).ok()?,
51-
)?;
42+
let trade_fee = self.trading_fee(source_amount)?;
43+
let owner_fee = self.owner_trading_fee(source_amount)?;
5244

5345
let amount_swapped = source_amount
5446
.checked_sub(trade_fee)?
@@ -75,6 +67,24 @@ impl CurveCalculator for FlatCurve {
7567
)
7668
}
7769

70+
/// Calculate the trading fee in trading tokens
71+
fn trading_fee(&self, trading_tokens: u128) -> Option<u128> {
72+
calculate_fee(
73+
trading_tokens,
74+
u128::try_from(self.trade_fee_numerator).ok()?,
75+
u128::try_from(self.trade_fee_denominator).ok()?,
76+
)
77+
}
78+
79+
/// Calculate the owner trading fee in trading tokens
80+
fn owner_trading_fee(&self, trading_tokens: u128) -> Option<u128> {
81+
calculate_fee(
82+
trading_tokens,
83+
u128::try_from(self.owner_trade_fee_numerator).ok()?,
84+
u128::try_from(self.owner_trade_fee_denominator).ok()?,
85+
)
86+
}
87+
7888
/// Calculate the host fee based on the owner fee, only used in production
7989
/// situations where a program is hosted by multiple frontends
8090
fn host_fee(&self, owner_fee: u128) -> Option<u128> {
@@ -95,6 +105,10 @@ impl IsInitialized for FlatCurve {
95105
impl Sealed for FlatCurve {}
96106
impl Pack for FlatCurve {
97107
const LEN: usize = 64;
108+
fn pack_into_slice(&self, output: &mut [u8]) {
109+
(self as &dyn DynPack).pack_into_slice(output);
110+
}
111+
98112
fn unpack_from_slice(input: &[u8]) -> Result<FlatCurve, ProgramError> {
99113
let input = array_ref![input, 0, 64];
100114
#[allow(clippy::ptr_offset_with_cast)]
@@ -119,10 +133,6 @@ impl Pack for FlatCurve {
119133
host_fee_denominator: u64::from_le_bytes(*host_fee_denominator),
120134
})
121135
}
122-
123-
fn pack_into_slice(&self, output: &mut [u8]) {
124-
(self as &dyn DynPack).pack_into_slice(output);
125-
}
126136
}
127137

128138
impl DynPack for FlatCurve {

token-swap/program/src/curve/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pub mod base;
44
pub mod calculator;
55
pub mod constant_product;
66
pub mod flat;
7+
pub mod stable;

0 commit comments

Comments
 (0)