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