From e6c4793cfa166a90b84c43c0f2b5b1c4965c3c41 Mon Sep 17 00:00:00 2001 From: bengtlofgren Date: Sun, 2 Feb 2025 21:47:22 +0000 Subject: [PATCH] readme added --- proto/penumbra/penumbra/core/app/v1/app.proto | 1 + .../core/component/tokenfactory/v1/README.md | 116 ++++++++++++ .../component/tokenfactory/v1/factory.proto | 167 ++++++++++++++++++ proto/penumbra/penumbra/view/v1/view.proto | 1 + 4 files changed, 285 insertions(+) create mode 100644 proto/penumbra/penumbra/core/component/tokenfactory/v1/README.md create mode 100644 proto/penumbra/penumbra/core/component/tokenfactory/v1/factory.proto diff --git a/proto/penumbra/penumbra/core/app/v1/app.proto b/proto/penumbra/penumbra/core/app/v1/app.proto index 7d9f1a99de..31cc43e6a5 100644 --- a/proto/penumbra/penumbra/core/app/v1/app.proto +++ b/proto/penumbra/penumbra/core/app/v1/app.proto @@ -12,6 +12,7 @@ import "penumbra/core/component/ibc/v1/ibc.proto"; import "penumbra/core/component/sct/v1/sct.proto"; import "penumbra/core/component/shielded_pool/v1/shielded_pool.proto"; import "penumbra/core/component/stake/v1/stake.proto"; +import "penumbra/core/compoenent/factory/v1/factory.proto"; import "penumbra/core/transaction/v1/transaction.proto"; // Query operations for the overall Penumbra application. diff --git a/proto/penumbra/penumbra/core/component/tokenfactory/v1/README.md b/proto/penumbra/penumbra/core/component/tokenfactory/v1/README.md new file mode 100644 index 0000000000..848aa0da57 --- /dev/null +++ b/proto/penumbra/penumbra/core/component/tokenfactory/v1/README.md @@ -0,0 +1,116 @@ +## Token Factory Implementation Idea + +### User story + +The token factory will works as following: + +#### Action TokenFactoryCreateWithBondingCurve + +##### Input to TokenFactoryCreateWithBondingCurve + +1. The user will submit the token metadata for the token they want to create (token name, token decimals, token max supply, image, whatever other metadata) +(Optional) 2. The user may specify the base token for the bonding curve. This may also just be hardcoded as UM (this has some benefits for liquidity composition later down the line - and is simpler to understand) +(Optional) 3. The user may specify the bonding curve max size. What this means is the total base token in 2 that is necessary to be deposited for the curve to be "fully bonded". + +##### Output of TokenFactoryCreateWithBondingCurve + +This action will output the following: + +1. A BondingCurveNFT +(Optional) 2. A MintTokenNFT (this can be ingrained into 1 potentially) + +The BondingCurveNFT will specify the price to mint a token. As more tokens get minted, the BondingCurveNFT is eventually consumed and a new one is created (with a higher price). This can be done once per token mint, or it can be done for set limits of the token. When a token is minted, the supply of that token goes up, and to mint a token the user must call the action `TokenFactoryMint`. + +#### Action TokenFactoryMint + +##### Input to TokenFactoryMint + +1. The user specifies the asset of the token from the tokenFactory to mint, and the amount to mint, as well as the corresponding correct amount of base token that corresponds to this mint. + +##### Output of TokenFactoryMint + +1. The base_supply in the BondingCurveNFT is increased corresponding to how much of the base token was submitted. The nft is burnt depending on if the supply has surpassed a certain threshold depending on the curve. +2. The minted tokens are given to the user. + +#### Action TokenFactoryBurn + +Directly opposite to `Action TokenFactoryMint`. This should be able to consume nfts and make their sequence number go backwards and price decrease. + +### Objects + +#### BondingCurveNFT + +```rust +pub struct BondingCurveNFT { + pub sequence: u8, + pub base_supplied: u64, + pub base_supply_cap: u64 + pub current_price: f64 // Not a float in practice, only for conceptualisation purposes + pub exponential_rate: f64 +} + +impl BondingCurveNFT { + // Not actually a float, just for conceptualisation purposes + /// Figure out how many tokens that are minted from a certain collateral deposit + pub fn get_mint_amount(&self, input_base: u64) -> u64 { + // Geometric sum formula = a(1-r^n)/(1-r) + // where: + // a = first term + // r = common ratio + // n = number of terms + (self.exponential_rate).log2() * (self.sequence as i32) = (input_base * (self.exponential_rate - 1) + 1).log2() + } + + pub fn try_mint(&self) -> Option { + let new_base_supplied = self.base_supplied + self.current_price + if new_base_supplied >= + Self { + sequence = self.sequence + 1, + base_supplied = self.base_supplied + self.current_price, + base_supply_cap = self.base_supply_cap, + current_price = self.get_next_price(), + exponential_rate = self.exponential_rate + } + } + + /// Assuming the exponential factor is 2 + pub fn get_next_price(&self) -> u64 { + self.exponential_rate * current_price // Or exponential_rate ** sequence + } + + /// Find sequence for the mint + pub fn get_sequence_for_mint(&self) -> u8 { + // If log is cheaply implemented: + // Use log2 to determine sequence number based on supply + ((self.base_supply as f64).log2() as u8) + } +} +``` + +### BondingCurve Completion + +The bonding curve is completed when the max supply is reached. The max base supply must be set to a number such that the geometric sum is solvable for an integer n. I.e + +$$ (r^n - 1)/(r-1) = \text{base_supply_cap} $$ + +has a solution for $n \in \mathbb{Z}^+$ + +## Privacy implications + +Privacy implication of this bonding curve. + +The total supply should always be observable, in the same way that an auction is observable to a user on Penumbra. When the user purchases or mints a new token, there should be no need to reveal any data, but if there ends up being a need for this, they can always shield the tokens afterward. + +## Fairness implications + +By keeping the same structure as the autction nft, it should be possible to guarantee the same fairness implications as for dutch auctions on Penumbra, and because it is an automated market maker in this case, there is no need for doing any matchmaking at all. Instead, price is predetermined by supply. Before the bonding period is complete, there may be users wanting to sell or buy tokens outside of the bonding curve pricing. This would have to be done on the auction interface rather than the minting/burning interface. + +### Frontrunning protection + +Because I don't fully understand frontrunning protection on penumbra, I'm not entirely sure what gurantees are carried over to this model. This will become more clear with time. + +## Considerations + +It needs to be thought about as to what flexibility we want to have for this token factory on penumbra. + +The more flexibility will mean more complications and development time. We also need to consider simplicity and its effect on the longer term improvements, such as liquidity fungibility across other factories. \ No newline at end of file diff --git a/proto/penumbra/penumbra/core/component/tokenfactory/v1/factory.proto b/proto/penumbra/penumbra/core/component/tokenfactory/v1/factory.proto new file mode 100644 index 0000000000..b090c7a344 --- /dev/null +++ b/proto/penumbra/penumbra/core/component/tokenfactory/v1/factory.proto @@ -0,0 +1,167 @@ +syntax = "proto3"; +package penumbra.core.component.tokenfactory.v1; + +import "google/protobuf/any.proto"; +import "penumbra/core/asset/v1/asset.proto"; +import "penumbra/core/num/v1/num.proto"; + +// The configuration parameters for the token factory component. +message TokenFactoryParameters {} + + +// Query operations for the token factory component. +service QueryService { + // Get the current state of a token factory by ID. + rpc TokenFactoryStateById(TokenFactoryStateByIdRequest) returns (TokenFactoryStateByIdResponse); +} + +message TokenFactoryStateByIdRequest { + TokenFactoryId id = 1; +} + +message TokenFactoryStateByIdResponse { + // If present, the state of the token factory. If not present, no such token factory is known. + TokenFactoryState state = 1; +} + +// A unique identifier for a token factory, obtained from hashing the nonce +message TokenFactoryId { + bytes inner = 1; +} + +// A bearer NFT tracking minting authority for a token factory +message TokenFactoryNft { + TokenFactoryId id = 1; + uint64 seq = 2; +} + +// Describes the creation of a new token +message TokenFactoryDescription { + // The metadata for the new token + asset.v1.Metadata metadata = 1; + // The initial supply of the token + num.v1.Amount initial_supply = 2; + // A random nonce used to generate the token factory ID + bytes nonce = 3; +} + +message TokenFactoryState { + // The sequence number of the token factory state + uint64 seq = 1; + // The total supply of the token + num.v1.Amount total_supply = 2; +} + +// Initiates creation of a new token +message ActionTokenFactoryCreate { + TokenFactoryDescription description = 1; +} + +// Mints additional tokens using the token factory NFT +message ActionTokenFactoryMint { + // The token factory to mint from + TokenFactoryId factory_id = 1; + // The amount to mint + num.v1.Amount amount = 2; + // The sequence number of the mint + uint64 seq = 3; +} + +// Burns tokens explicitly +message ActionBurn { + // The value to burn + asset.v1.Value value = 1; +} + +// View types for client display + +message ActionTokenFactoryCreateView { + ActionTokenFactoryCreate action = 1; + TokenFactoryId factory_id = 2; + asset.v1.Metadata token_metadata = 3; +} + +message ActionTokenFactoryMintView { + ActionTokenFactoryMint action = 1; + asset.v1.Metadata token_metadata = 2; +} + +message ActionBurnView { + ActionBurn action = 1; + asset.v1.ValueView value = 2; +} + +// Events + +message EventTokenFactoryCreated { + TokenFactoryId factory_id = 1; + TokenFactoryDescription description = 2; +} + +message EventTokenFactoryMinted { + TokenFactoryId factory_id = 1; + num.v1.Amount amount = 2; + TokenFactoryState state = 3; +} + +message EventTokensBurned { + asset.v1.Value value = 1; +} + +// Circuit breaker events similar to auction component +message EventValueCircuitBreakerCredit { + asset.v1.AssetId asset_id = 1; + num.v1.Amount previous_balance = 2; + num.v1.Amount new_balance = 3; +} + +message EventValueCircuitBreakerDebit { + asset.v1.AssetId asset_id = 1; + num.v1.Amount previous_balance = 2; + num.v1.Amount new_balance = 3; +} + +// Describes the bonding curve configuration +message BondingCurveConfig { + // The base token (maybe hardcode this to be UM) used for the bonding curve + asset.v1.AssetId base_token = 1; + // The deposit limit that equates to "bonding curve completed". This may want to be hardcoded so that each token has a predictable total supply and the price of each token is more understadable + num.v1.Amount deposit_limit = 2; + // Maximum supply cap for the token. This may want to be hardcoded so that each token has a predictable total supply and the price of each token is more understadable + num.v1.Amount max_supply = 3; +} + +// A bearer NFT tracking the bonding curve state +message BondingCurveNft { + TokenFactoryId factory_id = 1; + uint32 section = 2; // 1-100, represents current position in the curve + num.v1.Amount current_price = 3; // Price = deposit_limit/(max_supply-current_supply) (but this is stepwise approximated) + num.v1.Amount current_supply = 4; // Current total supply +} + +// Creates a new token with bonding curve +message ActionTokenFactoryCreateWithBondingCurve { + // The metadata for the new token + asset.v1.Metadata metadata = 1; + // A random nonce used to generate the token factory ID + bytes nonce = 2; + // Bonding curve configuration + BondingCurveConfig curve_config = 3; +} + +// View type for client display +message ActionTokenFactoryCreateWithBondingCurveView { + ActionTokenFactoryCreateWithBondingCurve action = 1; + TokenFactoryId factory_id = 2; + asset.v1.Metadata token_metadata = 3; + BondingCurveNft initial_curve_state = 4; +} + +// Additional event for bonding curve updates +message EventBondingCurveUpdated { + TokenFactoryId factory_id = 1; + uint32 previous_section = 2; + uint32 new_section = 3; + num.v1.Amount previous_price = 4; + num.v1.Amount new_price = 5; +} diff --git a/proto/penumbra/penumbra/view/v1/view.proto b/proto/penumbra/penumbra/view/v1/view.proto index 1c9a3cfd72..eb06901f0b 100644 --- a/proto/penumbra/penumbra/view/v1/view.proto +++ b/proto/penumbra/penumbra/view/v1/view.proto @@ -13,6 +13,7 @@ import "penumbra/core/component/ibc/v1/ibc.proto"; import "penumbra/core/component/sct/v1/sct.proto"; import "penumbra/core/component/shielded_pool/v1/shielded_pool.proto"; import "penumbra/core/component/stake/v1/stake.proto"; +import "penumbra/core/component/factory/v1/factory.proto"; import "penumbra/core/keys/v1/keys.proto"; import "penumbra/core/num/v1/num.proto"; import "penumbra/core/transaction/v1/transaction.proto";