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}