diff --git a/ibctesting/genesis_transfer_test.go b/ibctesting/genesis_transfer_test.go index 62dc219b6..7c6518346 100644 --- a/ibctesting/genesis_transfer_test.go +++ b/ibctesting/genesis_transfer_test.go @@ -80,7 +80,7 @@ func (s *transferGenesisSuite) TestNoIRO() { // In this case, the genesis transfer is required // regular transfers should fail until the genesis transfer is done func (s *transferGenesisSuite) TestIRO() { - amt := math.NewIntFromUint64(10000000000000000000) + amt := math.NewIntFromUint64(1_000_000).MulRaw(1e18) rollapp := s.hubApp().RollappKeeper.MustGetRollapp(s.hubCtx(), rollappChainID()) denom := rollapp.GenesisInfo.NativeDenom.Base diff --git a/x/iro/keeper/claim.go b/x/iro/keeper/claim.go index c475bb30e..6560e03c6 100644 --- a/x/iro/keeper/claim.go +++ b/x/iro/keeper/claim.go @@ -20,6 +20,9 @@ func (m msgServer) Claim(ctx context.Context, req *types.MsgClaim) (*types.MsgCl } // Claim claims the FUT token for the real RA token +// +// This function allows a user to claim their RA tokens by burning their FUT tokens. +// It burns *all* the FUT tokens the claimer has, and sends the equivalent amount of RA tokens to the claimer. func (k Keeper) Claim(ctx sdk.Context, planId string, claimer sdk.AccAddress) error { plan, found := k.GetPlan(ctx, planId) if !found { diff --git a/x/iro/keeper/create_plan.go b/x/iro/keeper/create_plan.go index 328e09c20..999bf86fb 100644 --- a/x/iro/keeper/create_plan.go +++ b/x/iro/keeper/create_plan.go @@ -21,7 +21,8 @@ import ( ) // This function is used to create a new plan for a rollapp. -// Validations on the request: +// Non stateful validation happens on the req.ValidateBasic() method +// Stateful validations on the request: // - The rollapp must exist, with no IRO plan // - The rollapp must be owned by the creator of the plan // - The rollapp PreLaunchTime must be in the future @@ -74,6 +75,13 @@ func (m msgServer) CreatePlan(goCtx context.Context, req *types.MsgCreatePlan) ( } // CreatePlan creates a new IRO plan for a rollapp +// This function performs the following steps: +// 1. Sets the IRO plan to the rollapp with the specified pre-launch time. +// 2. Mints the allocated amount of tokens for the rollapp. +// 3. Creates a new plan with the provided parameters and validates it. +// 4. Creates a new module account for the IRO plan. +// 5. Charges the creation fee from the rollapp owner to the plan's module account. +// 6. Stores the plan in the keeper. func (k Keeper) CreatePlan(ctx sdk.Context, allocatedAmount math.Int, start, preLaunchTime time.Time, rollapp rollapptypes.Rollapp, curve types.BondingCurve, incentivesParams types.IncentivePlanParams) (string, error) { err := k.rk.SetIROPlanToRollapp(ctx, &rollapp, preLaunchTime) if err != nil { @@ -86,6 +94,10 @@ func (k Keeper) CreatePlan(ctx sdk.Context, allocatedAmount math.Int, start, pre } plan := types.NewPlan(k.GetNextPlanIdAndIncrement(ctx), rollapp.RollappId, allocation, curve, start, preLaunchTime, incentivesParams) + if err := plan.ValidateBasic(); err != nil { + return "", errors.Join(gerrc.ErrInvalidArgument, err) + } + // Create a new module account for the IRO plan _, err = k.CreateModuleAccountForPlan(ctx, plan) if err != nil { diff --git a/x/iro/keeper/settle.go b/x/iro/keeper/settle.go index b74e7c21a..18ff5a608 100644 --- a/x/iro/keeper/settle.go +++ b/x/iro/keeper/settle.go @@ -23,6 +23,12 @@ func (k Keeper) AfterTransfersEnabled(ctx sdk.Context, rollappId, rollappIBCDeno } // Settle settles the iro plan with the given rollappId +// +// This function performs the following steps: +// - Validates that the "TotalAllocation.Amount" of the RA token are available in the module account. +// - Burns any unsold FUT tokens in the module account. +// - Marks the plan as settled, allowing users to claim tokens. +// - Uses the raised DYM and unsold tokens to bootstrap the rollapp's liquidity pool. func (k Keeper) Settle(ctx sdk.Context, rollappId, rollappIBCDenom string) error { plan, found := k.GetPlanByRollapp(ctx, rollappId) if !found { @@ -69,7 +75,13 @@ func (k Keeper) Settle(ctx sdk.Context, rollappId, rollappIBCDenom string) error return nil } -// Bootstrap liquidity pool with the raised DYM and unsold tokens +// bootstrapLiquidityPool bootstraps the liquidity pool with the raised DYM and unsold tokens. +// +// This function performs the following steps: +// - Sends the raised DYM to the IRO module to be used as the pool creator. +// - Determines the required pool liquidity amounts to fulfill the last price. +// - Creates a balancer pool with the determined tokens and DYM. +// - Uses leftover tokens as incentives to the pool LP token holders. func (k Keeper) bootstrapLiquidityPool(ctx sdk.Context, plan types.Plan) error { unallocatedTokens := plan.TotalAllocation.Amount.Sub(plan.SoldAmt) // assumed > 0, as we enforce it in the Buy function raisedDYM := k.BK.GetBalance(ctx, plan.GetAddress(), appparams.BaseDenom) // assumed > 0, as we enforce it by IRO creation fee @@ -81,7 +93,7 @@ func (k Keeper) bootstrapLiquidityPool(ctx sdk.Context, plan types.Plan) error { } // find the tokens needed to bootstrap the pool, to fulfill last price - tokens, dym := determineLimitingFactor(unallocatedTokens, raisedDYM.Amount, plan.SpotPrice()) + tokens, dym := calcLiquidityPoolTokens(unallocatedTokens, raisedDYM.Amount, plan.SpotPrice()) rollappLiquidityCoin := sdk.NewCoin(plan.SettledDenom, tokens) dymLiquidityCoin := sdk.NewCoin(appparams.BaseDenom, dym) @@ -124,7 +136,11 @@ func (k Keeper) bootstrapLiquidityPool(ctx sdk.Context, plan types.Plan) error { return nil } -func determineLimitingFactor(unsoldRATokens, raisedDYM math.Int, settledTokenPrice math.LegacyDec) (RATokens, dym math.Int) { +// calcLiquidityPoolTokens determines the tokens and DYM to be used for bootstrapping the liquidity pool. +// +// This function calculates the required DYM based on the settled token price and compares it with the raised DYM. +// It returns the amount of RA tokens and DYM to be used for bootstrapping the liquidity pool so it fulfills the last price. +func calcLiquidityPoolTokens(unsoldRATokens, raisedDYM math.Int, settledTokenPrice math.LegacyDec) (RATokens, dym math.Int) { requiredDYM := settledTokenPrice.MulInt(unsoldRATokens).TruncateInt() // if raisedDYM is less than requiredDYM, than DYM is the limiting factor @@ -139,7 +155,7 @@ func determineLimitingFactor(unsoldRATokens, raisedDYM math.Int, settledTokenPri dym = requiredDYM } - // for the extreme edge case where required liquidity truncated to 0 + // for the edge cases where required liquidity truncated to 0 // we use what we have as it guaranteed to be more than 0 if dym.IsZero() { dym = raisedDYM diff --git a/x/iro/types/msgs.go b/x/iro/types/msgs.go index e927155d9..46e0ae0c6 100644 --- a/x/iro/types/msgs.go +++ b/x/iro/types/msgs.go @@ -15,6 +15,10 @@ var ( _ sdk.Msg = &MsgUpdateParams{} ) +// ValidateBasic performs basic validation checks on the MsgCreatePlan message. +// It ensures that the owner address is valid, the bonding curve is valid, the allocated amount +// is greater than the minimum token allocation, the pre-launch time is before the start time, +// and the incentive plan parameters are valid. func (m *MsgCreatePlan) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(m.Owner) if err != nil {