Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

launchpad_withdraw.gno

5.41 Kb · 175 lines
  1package launchpad
  2
  3import (
  4	"chain"
  5	"chain/runtime"
  6	"errors"
  7	"time"
  8
  9	gnsmath "gno.land/p/gnoswap/gnsmath/v1"
 10	"gno.land/p/gnoswap/utils/v1"
 11	ufmt "gno.land/p/nt/ufmt/v0"
 12
 13	"gno.land/r/gnoswap/common"
 14	"gno.land/r/gnoswap/emission"
 15	"gno.land/r/gnoswap/halt/v1"
 16	"gno.land/r/gnoswap/launchpad"
 17
 18	gov_staker "gno.land/r/gnoswap/gov/staker"
 19)
 20
 21// CollectDepositGns withdraws the original GNS deposit after the lock period
 22// ends. Before returning the principal, it settles any claimable project-token
 23// reward for the deposit.
 24//
 25// Parameters:
 26//   - _: Noncrossing implementation-call discriminator; pass 0.
 27//   - rlm: Current realm context forwarded by the launchpad proxy; invalid contexts return an error.
 28//   - depositID: ID of the deposit to withdraw.
 29//
 30// Returns:
 31//   - amount: original GNS deposit amount returned to the depositor, in base units.
 32//   - err: non-nil for a spoofed-realm call; other withdrawal failures panic before returning.
 33//
 34// Only callable by deposit owner when the current time is strictly after the
 35// selected tier's end time.
 36func (lp *launchpadV1) CollectDepositGns(_ int, rlm realm, depositID string) (int64, error) {
 37	if !rlm.IsCurrent() {
 38		return 0, errors.New(errSpoofedRealm)
 39	}
 40
 41	halt.AssertIsNotHaltedWithdraw()
 42
 43	previousRealm := rlm.Previous()
 44
 45	caller := previousRealm.Address()
 46	lp.assertIsDepositOwner(depositID, caller)
 47
 48	emission.MintAndDistributeGns(cross(rlm))
 49
 50	deposit := lp.mustGetDeposit(depositID)
 51	targetProjectTierID := deposit.ProjectTierID()
 52	currentHeight := runtime.ChainHeight()
 53	currentTime := time.Now().Unix()
 54
 55	// Collect reward before withdrawal
 56	rewardTokenPath, rewardAmount, err := lp.collectDepositReward(0, rlm, deposit, currentHeight, currentTime)
 57	if err != nil {
 58		panic(err)
 59	}
 60
 61	beforeTotalGNSStakedAmount := lp.store.GetTotalGNSStakedAmount()
 62
 63	recipient, withdrawalAmount, err := lp.withdrawDeposit(0, rlm, deposit, runtime.ChainHeight(), currentTime)
 64	if err != nil {
 65		panic(err.Error())
 66	}
 67
 68	afterTotalGNSStakedAmount := gnsmath.SafeSubInt64(beforeTotalGNSStakedAmount, withdrawalAmount)
 69	if err := lp.store.SetTotalGNSStakedAmount(0, rlm, afterTotalGNSStakedAmount); err != nil {
 70		panic(err.Error())
 71	}
 72
 73	// Cross-realm governance update before token transfers
 74	unStakeGovernance(0, rlm, recipient, withdrawalAmount)
 75
 76	// Transfer reward token to depositor
 77	if rewardAmount > 0 {
 78		common.SafeGRC20Transfer(0, rlm, rewardTokenPath, deposit.Depositor(), rewardAmount)
 79
 80		chain.Emit(
 81			"CollectRewardByDepositId",
 82			"prevAddr", caller.String(),
 83			"prevRealm", previousRealm.PkgPath(),
 84			"depositId", depositID,
 85			"targetProjectTierId", targetProjectTierID,
 86			"amount", utils.FormatInt(rewardAmount),
 87		)
 88	}
 89
 90	// Transfer the original GNS deposit back to the depositor
 91	common.SafeGRC20Transfer(0, rlm, GNS_TOKEN_KEY, deposit.Depositor(), withdrawalAmount)
 92
 93	chain.Emit(
 94		"CollectDepositGns",
 95		"prevAddr", caller.String(),
 96		"prevRealm", previousRealm.PkgPath(),
 97		"depositId", depositID,
 98		"targetProjectTierId", targetProjectTierID,
 99		"amount", utils.FormatInt(withdrawalAmount),
100		"totalGNSStakedAmount", utils.FormatInt(afterTotalGNSStakedAmount),
101		"recipient", recipient.String(),
102	)
103
104	return withdrawalAmount, nil
105}
106
107// withdrawDeposit withdraws a deposit and updates the reward manager.
108func (lp *launchpadV1) withdrawDeposit(_ int, rlm realm, deposit launchpad.Deposit, currentHeight, currentTime int64) (address, int64, error) {
109	// Input validation
110	if currentTime <= 0 {
111		return "", 0, makeErrorWithDetails(errInvalidTime, "currentTime must be positive")
112	}
113
114	// State validation
115	if deposit.IsWithdrawn() {
116		return "", 0, makeErrorWithDetails(errAlreadyCollected, ufmt.Sprintf("(%s)", deposit.ID()))
117	}
118
119	if !deposit.IsEnded(currentTime) {
120		return "", 0, makeErrorWithDetails(errNotYetEndedProject, ufmt.Sprintf("(%s)", deposit.ID()))
121	}
122
123	// Get project and tier information
124	project, err := lp.getProject(deposit.ProjectID())
125	if err != nil {
126		return "", 0, err
127	}
128
129	projectTier, err := getProjectTier(project, deposit.Tier())
130	if err != nil {
131		return "", 0, err
132	}
133
134	// Get reward manager and update rewards before withdrawal
135	rewardManager, err := lp.getProjectTierRewardManager(projectTier.ID())
136	if err != nil {
137		return "", 0, err
138	}
139
140	// Update rewards with current deposit amount
141	err = updateRewardPerDepositX128(rewardManager, getTierCurrentDepositAmount(projectTier), currentTime)
142	if err != nil {
143		return "", 0, err
144	}
145
146	// Process withdrawal from project tier
147	withdrawToTier(projectTier, deposit)
148	project.SetTier(deposit.Tier(), projectTier)
149
150	// Finalize withdrawal
151	deposit, withdrawalAmount := withdrawDeposit(deposit, currentHeight, currentTime)
152
153	// Remove the reward state and its reserved active claim after withdrawal.
154	if err := removeRewardState(rewardManager, deposit.ID()); err != nil {
155		return "", 0, err
156	}
157
158	// Persist the updated value; the caller's deposit value is unchanged.
159	deposits := lp.store.GetDeposits()
160	deposits.Set(deposit.ID(), deposit)
161
162	// Save the modified state back
163	if err := lp.store.SetDeposits(0, rlm, deposits); err != nil {
164		return "", 0, err
165	}
166
167	emitUpdateLaunchpadRewardAccumulation(projectTier.ID(), rewardManager, getTierCurrentDepositAmount(projectTier))
168
169	return project.Recipient(), withdrawalAmount, nil
170}
171
172// unStakeGovernance removes the staked amount from governance system
173func unStakeGovernance(_ int, rlm realm, recipient address, withdrawalAmount int64) {
174	gov_staker.SetAmountByProjectWallet(cross(rlm), recipient, withdrawalAmount, false)
175}