Skip to content
Closed
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
39 changes: 39 additions & 0 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ serde_json = "1.0"
serde_bytes = "0.11.17"
ordinals = "0.0.14"
rust_decimal = "1.37.1"
bip322 = "0.0.9"

[features]
testnet = []
Expand Down
186 changes: 161 additions & 25 deletions src/canister.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
pool::{self, CoinMeta, PoolState},
pool::{self, CoinMeta, Liquidity, PoolState, PoolTemplate},
ExchangeError,
};
use candid::{CandidType, Deserialize, Principal};
Expand Down Expand Up @@ -79,6 +79,43 @@ pub fn recover() {
});
}

/// message -> {pool}:{lock_time_in_blocks}
#[update]
pub fn lock_lp(addr: String, message: String, sig: String) -> Result<(), ExchangeError> {
crate::ensure_online()?;
bip322::verify_simple_encoded(&addr, &message, &sig)
.map_err(|_| ExchangeError::InvalidSignature)?;
let lock: Vec<&str> = message.split(':').collect();
let pool = lock.get(0).ok_or(ExchangeError::InvalidLockMessage)?;
let lock_time = lock
.get(1)
.and_then(|s| s.parse::<u32>().ok())
.ok_or(ExchangeError::InvalidLockMessage)?;
(lock_time >= crate::min_lock_time())
.then_some(())
.ok_or(ExchangeError::InvalidLockMessage)?;
let max_block = crate::get_max_block().ok_or(ExchangeError::InvalidLockMessage)?;
let lock_until = max_block
.block_height
.checked_add(lock_time)
.unwrap_or(u32::MAX);
crate::with_pool_mut(pool.to_string(), |p| {
let mut pool = p.ok_or(ExchangeError::InvalidPool)?;
let state = pool.states.last_mut().ok_or(ExchangeError::EmptyPool)?;
state
.lp_locks
.entry(addr.clone())
.and_modify(|t| {
if *t < lock_until {
*t = lock_until;
}
})
.or_insert(lock_until);
Ok(Some(pool))
})?;
Ok(())
}

#[query]
pub fn get_pool_state_chain(
addr: String,
Expand Down Expand Up @@ -117,8 +154,7 @@ pub fn get_tx_affected(txid: Txid) -> Option<TxRecord> {
crate::get_tx_affected(txid)
}

#[update]
pub async fn create(rune_id: CoinId) -> Result<String, ExchangeError> {
pub async fn create_pool(rune_id: CoinId, template: PoolTemplate) -> Result<String, ExchangeError> {
crate::ensure_online()?;
match crate::with_pool_name(&rune_id) {
Some(addr) => crate::with_pool(&addr, |pool| {
Expand Down Expand Up @@ -150,11 +186,25 @@ pub async fn create(rune_id: CoinId) -> Result<String, ExchangeError> {
symbol: name,
min_amount: 1,
};
crate::create_empty_pool(meta, untweaked_pubkey.clone())
crate::create_empty_pool(meta, template, untweaked_pubkey.clone())
}
}
}

#[update]
pub async fn create_with_template(
rune_id: CoinId,
template: PoolTemplate,
) -> Result<String, ExchangeError> {
// TODO whitelist
create_pool(rune_id, template).await
}

#[update]
pub async fn create(rune_id: CoinId) -> Result<String, ExchangeError> {
create_pool(rune_id, PoolTemplate::Standard).await
}

#[query]
pub fn query_pool(rune_id: CoinId) -> Result<PoolInfo, ExchangeError> {
crate::ensure_online()?;
Expand Down Expand Up @@ -218,6 +268,24 @@ pub async fn pre_donate(pool: String, input_sats: u64) -> Result<DonateIntention
})
}

#[query]
pub async fn pre_bi_donate(
pool: String,
input_sats: u64,
input_rune: CoinBalance,
) -> Result<DonateIntention, ExchangeError> {
crate::ensure_online()?;
let pool = crate::with_pool(&pool, |p| p.clone()).ok_or(ExchangeError::InvalidPool)?;
let state = pool.states.last().ok_or(ExchangeError::EmptyPool)?;
let (out_rune, out_sats) = pool.wish_to_bi_donate(input_sats, input_rune)?;
Ok(DonateIntention {
input: state.utxo.clone().ok_or(ExchangeError::EmptyPool)?,
nonce: state.nonce,
out_rune,
out_sats,
})
}

#[query]
pub fn pre_self_donate() -> Result<DonateIntention, ExchangeError> {
crate::ensure_online()?;
Expand Down Expand Up @@ -260,37 +328,28 @@ pub fn pre_extract_fee(addr: String) -> Result<ExtractFeeOffer, ExchangeError> {
})
}

#[derive(Clone, CandidType, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Liquidity {
pub user_incomes: u64,
pub user_share: u128,
pub total_share: u128,
}

#[query]
pub fn get_lp(addr: String, user_addr: String) -> Result<Liquidity, ExchangeError> {
crate::with_pool(&addr, |p| {
let pool = p.as_ref().ok_or(ExchangeError::InvalidPool)?;
pool.states
.last()
.and_then(|s| {
Some(Liquidity {
user_share: s.lp(&user_addr),
user_incomes: s.earning(&user_addr),
total_share: s.k,
})
})
.and_then(|s| Some(s.lp(&user_addr)))
.ok_or(ExchangeError::EmptyPool)
})
}

#[query]
pub fn get_all_lp(addr: String) -> Result<BTreeMap<String, u128>, ExchangeError> {
pub fn get_all_lp(addr: String) -> Result<BTreeMap<String, Liquidity>, ExchangeError> {
crate::with_pool(&addr, |p| {
let pool = p.as_ref().ok_or(ExchangeError::InvalidPool)?;
pool.states
.last()
.map(|s| s.lp.clone())
.map(|s| {
s.lp.iter()
.map(|(addr, _)| (addr.clone(), s.lp(addr)))
.collect()
})
.ok_or(ExchangeError::EmptyPool)
})
}
Expand Down Expand Up @@ -355,7 +414,9 @@ pub fn pre_withdraw_liquidity(
crate::ensure_online()?;
crate::with_pool(&pool_addr, |p| {
let pool = p.as_ref().ok_or(ExchangeError::InvalidPool)?;
let (btc, rune_output, _) = pool.available_to_withdraw(&user_addr, share)?;
let max_block = crate::get_max_block().ok_or(ExchangeError::BlockSyncing)?;
let (btc, rune_output, _) =
pool.available_to_withdraw(&user_addr, share, max_block.block_height)?;
let state = pool.states.last().expect("already checked");
Ok(WithdrawalOffer {
input: state.utxo.clone().expect("already checked"),
Expand All @@ -371,6 +432,31 @@ pub fn pre_withdraw_liquidity(
})
}

#[derive(Clone, CandidType, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PreClaimOutput {
pub input: Utxo,
pub claim_sats: u64,
pub nonce: u64,
}

#[query]
pub fn pre_claim_revenue(
pool_addr: String,
user_addr: String,
) -> Result<PreClaimOutput, ExchangeError> {
crate::ensure_online()?;
crate::with_pool(&pool_addr, |p| {
let pool = p.as_ref().ok_or(ExchangeError::InvalidPool)?;
let sats = pool.available_to_claim(&user_addr)?;
let state = pool.states.last().expect("already checked");
Ok(PreClaimOutput {
input: state.utxo.clone().expect("already checked"),
claim_sats: sats,
nonce: state.nonce,
})
})
}

#[derive(Eq, PartialEq, CandidType, Clone, Debug, Deserialize, Serialize)]
pub struct LiquidityOffer {
pub inputs: Option<Utxo>,
Expand Down Expand Up @@ -410,7 +496,7 @@ pub fn pre_swap(id: String, input: CoinBalance) -> Result<SwapOffer, ExchangeErr
crate::with_pool(&id, |p| {
let pool = p.as_ref().ok_or(ExchangeError::InvalidPool)?;
let recent_state = pool.states.last().ok_or(ExchangeError::EmptyPool)?;
let (offer, _, _, price_impact) = pool.available_to_swap(input)?;
let (offer, _, _, _, price_impact) = pool.available_to_swap(input)?;
Ok(SwapOffer {
input: recent_state.utxo.clone().expect("already checked"),
output: offer,
Expand Down Expand Up @@ -626,14 +712,21 @@ pub async fn execute_tx(args: ExecuteTxArgs) -> ExecuteTxResponse {

let _guard = crate::ExecuteTxGuard::new(pool_addr.clone())
.ok_or(format!("Pool {0} Executing", pool_addr).to_string())?;
let pool = crate::with_pool(&pool_address, |p| p.clone())
let mut pool = crate::with_pool(&pool_address, |p| p.clone())
.ok_or(ExchangeError::InvalidPool.to_string())?;
match intention.action.as_ref() {
"add_liquidity" => {
let lock_time = match action_params.is_empty() {
true => 0,
false => action_params
.parse()
.map_err(|_| "action params \"lock_time\" required")?,
};
let (new_state, consumed) = pool
.validate_adding_liquidity(
txid,
nonce,
lock_time,
pool_utxo_spent,
pool_utxo_received,
input_coins,
Expand All @@ -646,8 +739,7 @@ pub async fn execute_tx(args: ExecuteTxArgs) -> ExecuteTxResponse {
.await
.map_err(|e| e.to_string())?;
}
crate::with_pool_mut(pool_address, |p| {
let mut pool = p.expect("already checked in pre_add_liquidity;qed");
crate::with_pool_mut(pool_address, |_| {
pool.commit(new_state);
Ok(Some(pool))
})
Expand Down Expand Up @@ -678,6 +770,29 @@ pub async fn execute_tx(args: ExecuteTxArgs) -> ExecuteTxResponse {
})
.map_err(|e| e.to_string())?;
}
"claim_revenue" => {
let (new_state, consumed) = pool
.validate_claiming_revenue(
txid,
nonce,
pool_utxo_spent,
pool_utxo_received,
action_params,
input_coins,
output_coins,
initiator,
)
.map_err(|e| e.to_string())?;
crate::psbt::sign(&mut psbt, &consumed, pool.base_id().to_bytes())
.await
.map_err(|e| e.to_string())?;
crate::with_pool_mut(pool_address, |p| {
let mut pool = p.expect("already checked in available_to_withdraw;qed");
pool.commit(new_state);
Ok(Some(pool))
})
.map_err(|e| e.to_string())?;
}
"extract_protocol_fee" => {
// enforce combination
(intention_index == 0)
Expand Down Expand Up @@ -766,6 +881,27 @@ pub async fn execute_tx(args: ExecuteTxArgs) -> ExecuteTxResponse {
})
.map_err(|e| e.to_string())?;
}
"bi_donate" => {
let (new_state, consumed) = pool
.validate_bi_donate(
txid,
nonce,
pool_utxo_spent,
pool_utxo_received,
input_coins,
output_coins,
)
.map_err(|e| e.to_string())?;
crate::psbt::sign(&mut psbt, &consumed, pool.base_id().to_bytes())
.await
.map_err(|e| e.to_string())?;
crate::with_pool_mut(pool_address, |p| {
let mut pool = p.expect("already checked in pre_swap;qed");
pool.commit(new_state);
Ok(Some(pool))
})
.map_err(|e| e.to_string())?;
}
"self_donate" => {
let (new_state, consumed) = pool
.validate_self_donate(txid, nonce, pool_utxo_spent, pool_utxo_received)
Expand Down
Loading