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