Skip to content

Commit

Permalink
Implement "Bank" extension trait for liquidity tournament (#5035)
Browse files Browse the repository at this point in the history
## Describe your changes

This trait is responsible for the actual accounting of moving funds
around, to be later consumed by the end epoch handler, which figures out
what portion of the rewards needs to go where. The bank will just
actually move fractions of its budget in the community pool to the
requested locations, atomically.

Testing deferred.

## Checklist before requesting a review

- [x] I have added guiding text to explain how a reviewer should test
these changes.

- [x] If this code contains consensus-breaking changes, I have added the
"consensus-breaking" label. Otherwise, I declare my belief that there
are not consensus-breaking changes, for the following reason:

  > yes indeed
  • Loading branch information
cronokirby authored Jan 31, 2025
1 parent f8dcd60 commit 1a5dccb
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

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

54 changes: 53 additions & 1 deletion crates/core/component/dex/src/component/position_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use async_trait::async_trait;
use cnidarium::{EscapedByteSlice, StateRead, StateWrite};
use futures::Stream;
use futures::StreamExt;
use penumbra_sdk_asset::{asset, Balance};
use penumbra_sdk_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID};
use penumbra_sdk_num::Amount;
use penumbra_sdk_proto::DomainType;
use penumbra_sdk_proto::{StateReadProto, StateWriteProto};
use tap::Tap;
Expand Down Expand Up @@ -488,6 +489,57 @@ pub trait PositionManager: StateWrite + PositionRead {

Ok(reserves)
}

/// This adds extra rewards in the form of staking tokens to the reserves of a position.
#[tracing::instrument(level = "debug", skip(self))]
async fn reward_position(
&mut self,
position_id: position::Id,
reward: Amount,
) -> anyhow::Result<()> {
let prev_state = self
.position_by_id(&position_id)
.await?
.ok_or_else(|| anyhow::anyhow!("rewarding unknown position {}", position_id))?;
// The new state is the result of adding the staking token to the reserves,
// or doing nothing if for some reason this position does not have the staking token.
let new_state = {
let mut new_state = prev_state.clone();
let pair = prev_state.phi.pair;
let to_increment = if pair.asset_1() == *STAKING_TOKEN_ASSET_ID {
&mut new_state.reserves.r1
} else if pair.asset_2() == *STAKING_TOKEN_ASSET_ID {
&mut new_state.reserves.r2
} else {
tracing::error!("pair {} does not contain staking asset", pair);
return Ok(());
};
*to_increment = to_increment.checked_add(&reward).expect(&format!(
"failed to add reward {} to reserves {}",
reward, *to_increment
));
// Ok, you'd think we'd be done here, alas, the [`guard_invalid_transitions`] function
// will complain if the position has already been withdrawn, but the sequence has not yet been incremented!
new_state.state = match prev_state.state {
position::State::Opened => position::State::Opened,
position::State::Closed => position::State::Closed,
position::State::Withdrawn { sequence } => position::State::Withdrawn {
sequence: sequence.saturating_add(1),
},
};
new_state
};
self.update_position(&position_id, Some(prev_state), new_state)
.await?;
// At this point, we can credit the VCB, because the update passed.
// This is a credit because the reward has moved value *into* the DEX.
self.dex_vcb_credit(Value {
asset_id: *STAKING_TOKEN_ASSET_ID,
amount: reward,
})
.await?;
Ok(())
}
}

impl<T: StateWrite + ?Sized + Chandelier> PositionManager for T {}
Expand Down
3 changes: 3 additions & 0 deletions crates/core/component/funding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ component = [
"cnidarium",
"penumbra-sdk-proto/cnidarium",
"penumbra-sdk-community-pool/component",
"penumbra-sdk-dex/component",
"penumbra-sdk-distributions/component",
"penumbra-sdk-governance/component",
"penumbra-sdk-sct/component",
Expand All @@ -26,6 +27,7 @@ parallel = [
"ark-groth16/parallel",
"decaf377/parallel",
"decaf377-rdsa/parallel",
"penumbra-sdk-dex/parallel",
"penumbra-sdk-tct/parallel",
"penumbra-sdk-shielded-pool/parallel",
"penumbra-sdk-governance/parallel"
Expand All @@ -45,6 +47,7 @@ futures = {workspace = true, optional = true}
metrics = {workspace = true, optional = true}
penumbra-sdk-asset = {workspace = true, default-features = true}
penumbra-sdk-community-pool = {workspace = true, default-features = false}
penumbra-sdk-dex = {workspace = true, default-features = false}
penumbra-sdk-distributions = {workspace = true, default-features = false}
penumbra-sdk-governance = {workspace = true, default-features = false}
penumbra-sdk-keys = {workspace = true, default-features = false}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use async_trait::async_trait;
use cnidarium::StateWrite;
use penumbra_sdk_asset::{Value, STAKING_TOKEN_ASSET_ID};
use penumbra_sdk_dex::component::PositionManager as _;
use penumbra_sdk_dex::lp::position;
use penumbra_sdk_keys::Address;
use penumbra_sdk_num::Amount;
use penumbra_sdk_sct::component::clock::EpochRead as _;
use penumbra_sdk_sct::CommitmentSource;
use penumbra_sdk_shielded_pool::component::NoteManager as _;
use penumbra_sdk_txhash::TransactionId;

#[allow(dead_code)]
#[async_trait]
/// The bank strictly controls issuance of rewards in the liquidity tournament.
///
/// This ensures that rewards do not exceed the issuance budget, and are immediately
/// debited from the appropriate source (which happens to be the community pool),
/// and credited towards the appropriate destination (i.e. positions or new notes).
pub trait Bank: StateWrite + Sized {
/// Move a fraction of our issuance budget towards an address, by minting a note.
async fn reward_to_voter(
&mut self,
reward: Amount,
voter: &Address,
tx_hash: TransactionId,
) -> anyhow::Result<()> {
let epoch = self
.get_current_epoch()
.await
.expect("should be able to read current epoch");
self.mint_note(
Value {
asset_id: *STAKING_TOKEN_ASSET_ID,
amount: reward,
},
voter,
CommitmentSource::LiquidityTournamentReward {
epoch: epoch.index,
tx_hash,
},
)
.await?;
Ok(())
}

/// Move a fraction of our issuance budget towards a position, increasing its reserves.
async fn reward_to_position(&mut self, reward: Amount, lp: position::Id) -> anyhow::Result<()> {
self.reward_position(lp, reward).await?;
Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod bank;
pub mod nullifier;
pub mod votes;

0 comments on commit 1a5dccb

Please sign in to comment.