Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

readme added #5042

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions proto/penumbra/penumbra/core/app/v1/app.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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";

Check failure on line 15 in proto/penumbra/penumbra/core/app/v1/app.proto

View workflow job for this annotation

GitHub Actions / Lint protobuf

import "penumbra/core/compoenent/factory/v1/factory.proto": file does not exist
import "penumbra/core/transaction/v1/transaction.proto";

// Query operations for the overall Penumbra application.
Expand Down
116 changes: 116 additions & 0 deletions proto/penumbra/penumbra/core/component/tokenfactory/v1/README.md
Original file line number Diff line number Diff line change
@@ -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<Self> {
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.
167 changes: 167 additions & 0 deletions proto/penumbra/penumbra/core/component/tokenfactory/v1/factory.proto
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions proto/penumbra/penumbra/view/v1/view.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading