Skip to content

Commit

Permalink
fix: IRO claimed amount accounting (#1620)
Browse files Browse the repository at this point in the history
  • Loading branch information
mtsitrin authored Dec 5, 2024
1 parent a401959 commit b44e6bd
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 6 deletions.
79 changes: 79 additions & 0 deletions x/iro/keeper/invariant_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package keeper_test

import (
"time"

sdk "github.com/cosmos/cosmos-sdk/types"

appparams "github.com/dymensionxyz/dymension/v3/app/params"
"github.com/dymensionxyz/dymension/v3/testutil/sample"
keeper "github.com/dymensionxyz/dymension/v3/x/iro/keeper"
"github.com/dymensionxyz/dymension/v3/x/iro/types"
)

func (s *KeeperTestSuite) TestInvariantAccounting() {
rollappId := s.CreateDefaultRollapp()
k := s.App.IROKeeper
curve := types.DefaultBondingCurve()
incentives := types.DefaultIncentivePlanParams()

startTime := time.Now()
endTime := startTime.Add(time.Hour)
amt := sdk.NewInt(1_000_000).MulRaw(1e18)
rollappDenom := "test_rollapp_denom"

// Create a plan
rollapp := s.App.RollappKeeper.MustGetRollapp(s.Ctx, rollappId)
planId, err := k.CreatePlan(s.Ctx, amt, startTime, endTime, rollapp, curve, incentives)
s.Require().NoError(err)
plan := k.MustGetPlan(s.Ctx, planId)
planDenom := plan.TotalAllocation.Denom

// Buy some tokens to create a non-zero sold amount
s.Ctx = s.Ctx.WithBlockTime(startTime.Add(time.Minute))
soldAmt := sdk.NewInt(1_000).MulRaw(1e18)
buyer := sample.Acc()
s.BuySomeTokens(planId, buyer, soldAmt)

// Check invariant before settlement - should pass
inv := keeper.InvariantAccounting(*k)
err = inv(s.Ctx)
s.Require().NoError(err)

// Fund module with RA tokens and settle the plan
s.FundModuleAcc(types.ModuleName, sdk.NewCoins(sdk.NewCoin(rollappDenom, amt)))
err = k.Settle(s.Ctx, rollappId, rollappDenom)
s.Require().NoError(err)

// Check invariant after settlement but before claims - should pass
err = inv(s.Ctx)
s.Require().NoError(err)

// Claim some tokens
err = k.Claim(s.Ctx, planId, buyer)
s.Require().NoError(err)

// Check invariant after claims - should pass
err = inv(s.Ctx)
s.Require().NoError(err)

// Artificially break invariant by minting IRO tokens to module
err = s.App.BankKeeper.MintCoins(s.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(planDenom, sdk.NewInt(1))))
s.Require().NoError(err)

// Check invariant - should be broken due to IRO tokens in module after settlement
err = inv(s.Ctx)
s.Require().Error(err)

// Clean up minted tokens
err = s.App.BankKeeper.BurnCoins(s.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(planDenom, sdk.NewInt(1))))
s.Require().NoError(err)

// Artificially break invariant by sending DYM to plan address
dymAmt := sdk.NewInt(100).MulRaw(1e18)
s.FundAcc(plan.GetAddress(), sdk.NewCoins(sdk.NewCoin(appparams.BaseDenom, dymAmt)))

// Check invariant - should be broken due to DYM tokens in plan after settlement
err = inv(s.Ctx)
s.Require().Error(err)
}
20 changes: 14 additions & 6 deletions x/iro/keeper/settle.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,18 @@ func (k Keeper) Settle(ctx sdk.Context, rollappId, rollappIBCDenom string) error
return errorsmod.Wrapf(gerrc.ErrInternal, "required: %s, available: %s", plan.TotalAllocation.String(), balance.String())
}

// "claims" the unsold FUT token
futBalance := k.BK.GetBalance(ctx, k.AK.GetModuleAddress(types.ModuleName), plan.TotalAllocation.Denom)
err := k.BK.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(futBalance))
// burn all the unsold IRO token.
iroTokenBalance := k.BK.GetBalance(ctx, k.AK.GetModuleAddress(types.ModuleName), plan.TotalAllocation.Denom)
err := k.BK.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(iroTokenBalance))
if err != nil {
return err
}

// claim the fee token
// we put it aside on plan creation and claims it now
tokenFee := math.NewIntWithDecimal(types.TokenCreationFee, int(plan.BondingCurve.SupplyDecimals()))
plan.ClaimedAmt = plan.ClaimedAmt.Add(tokenFee)

// mark the plan as `settled`, allowing users to claim tokens
plan.SettledDenom = rollappIBCDenom
k.SetPlan(ctx, plan)
Expand Down Expand Up @@ -87,11 +92,14 @@ func (k Keeper) Settle(ctx sdk.Context, rollappId, rollappIBCDenom string) error
// - 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) (poolID, gaugeID uint64, err error) {
tokenFee := math.NewIntWithDecimal(types.TokenCreationFee, int(plan.BondingCurve.SupplyDecimals()))
unallocatedTokens := plan.TotalAllocation.Amount.Sub(plan.SoldAmt.Sub(tokenFee)) // at least "reserve" amount of tokens (>0)
raisedDYM := k.BK.GetBalance(ctx, plan.GetAddress(), appparams.BaseDenom) // at least IRO creation fee (>0)
// claimable amount is kept in the module account and used for user's claims
claimableAmt := plan.SoldAmt.Sub(plan.ClaimedAmt)

// the remaining tokens are used to bootstrap the liquidity pool
unallocatedTokens := plan.TotalAllocation.Amount.Sub(claimableAmt)

// send the raised DYM to the iro module as it will be used as the pool creator
raisedDYM := k.BK.GetBalance(ctx, plan.GetAddress(), appparams.BaseDenom)
err = k.BK.SendCoinsFromAccountToModule(ctx, plan.GetAddress(), types.ModuleName, sdk.NewCoins(raisedDYM))
if err != nil {
return 0, 0, err
Expand Down

0 comments on commit b44e6bd

Please sign in to comment.