Smart Contract
VRFExtension
A.79f5b5b0f95a160b.VRFExtension
1import VaultCore from 0x79f5b5b0f95a160b
2
3access(all) contract VRFExtension {
4
5 access(all) let CADENCE_ARCH_VRF: Address
6 access(all) let BASIS_POINTS: UFix64
7 access(all) let ExtensionStoragePath: StoragePath
8 access(all) let ExtensionPublicPath: PublicPath
9
10 access(all) event EpochAdvanced(epochNumber: UInt64, startTime: UFix64, yieldPool: UFix64)
11 access(all) event RewardClaimed(user: Address, epoch: UInt64, won: Bool, baseYield: UFix64, actualPayout: UFix64, multiplier: UFix64)
12 access(all) event VRFMultiplierSet(user: Address, multiplier: UFix64)
13 access(all) event YieldAdded(amount: UFix64, epoch: UInt64)
14
15 access(self) var currentEpoch: UInt64
16 access(self) var epochDuration: UFix64
17 access(self) var lastEpochStart: UFix64
18 access(self) var epochsPerPayout: UInt64
19 access(self) var totalYieldPool: UFix64
20 access(self) var totalDistributed: UFix64
21
22 access(all) struct VRFMultiplierOption {
23 access(all) let multiplier: UFix64
24 access(all) let probability: UFix64
25
26 init(multiplier: UFix64, probability: UFix64) {
27 self.multiplier = multiplier
28 self.probability = probability
29 }
30 }
31
32 access(all) struct EpochData {
33 access(all) let epochNumber: UInt64
34 access(all) let startTime: UFix64
35 access(all) var endTime: UFix64
36 access(all) var totalYieldPool: UFix64
37 access(all) var totalDistributed: UFix64
38 access(all) var participantCount: UInt64
39 access(all) var finalized: Bool
40
41 init(epochNumber: UInt64, startTime: UFix64) {
42 self.epochNumber = epochNumber
43 self.startTime = startTime
44 self.endTime = startTime + VRFExtension.epochDuration
45 self.totalYieldPool = 0.0
46 self.totalDistributed = 0.0
47 self.participantCount = 0
48 self.finalized = false
49 }
50
51 access(contract) fun finalize() {
52 self.finalized = true
53 self.endTime = getCurrentBlock().timestamp
54 }
55
56 access(contract) fun addYield(amount: UFix64) {
57 self.totalYieldPool = self.totalYieldPool + amount
58 }
59
60 access(contract) fun recordDistribution(amount: UFix64) {
61 self.totalDistributed = self.totalDistributed + amount
62 }
63
64 access(contract) fun incrementParticipants() {
65 self.participantCount = self.participantCount + 1
66 }
67 }
68
69 access(all) struct UserEpochData {
70 access(all) let user: Address
71 access(all) let epoch: UInt64
72 access(all) let vrfMultiplier: UFix64
73 access(all) let depositAmount: UFix64
74 access(all) let claimed: Bool
75 access(all) let won: Bool
76 access(all) let payoutAmount: UFix64
77
78 init(user: Address, epoch: UInt64, vrfMultiplier: UFix64, depositAmount: UFix64, claimed: Bool, won: Bool, payoutAmount: UFix64) {
79 self.user = user
80 self.epoch = epoch
81 self.vrfMultiplier = vrfMultiplier
82 self.depositAmount = depositAmount
83 self.claimed = claimed
84 self.won = won
85 self.payoutAmount = payoutAmount
86 }
87 }
88
89 access(all) resource Extension {
90 access(self) let vaultCap: Capability<&VaultCore.Vault>
91 access(self) let epochs: {UInt64: EpochData}
92 access(self) let userEpochData: {Address: {UInt64: UserEpochData}}
93 access(self) let vrfMultipliers: {UFix64: VRFMultiplierOption}
94
95 init(vaultCap: Capability<&VaultCore.Vault>) {
96 self.vaultCap = vaultCap
97 self.epochs = {}
98 self.userEpochData = {}
99 self.vrfMultipliers = {}
100
101 self.epochs[VRFExtension.currentEpoch] = EpochData(
102 epochNumber: VRFExtension.currentEpoch,
103 startTime: VRFExtension.lastEpochStart
104 )
105
106 self.initializeVRFMultipliers()
107 }
108
109 access(self) fun initializeVRFMultipliers() {
110 self.vrfMultipliers[1.0] = VRFMultiplierOption(multiplier: 1.0, probability: 10000.0)
111 self.vrfMultipliers[2.0] = VRFMultiplierOption(multiplier: 2.0, probability: 5000.0)
112 self.vrfMultipliers[5.0] = VRFMultiplierOption(multiplier: 5.0, probability: 2000.0)
113 self.vrfMultipliers[10.0] = VRFMultiplierOption(multiplier: 10.0, probability: 1000.0)
114 self.vrfMultipliers[50.0] = VRFMultiplierOption(multiplier: 50.0, probability: 200.0)
115 self.vrfMultipliers[100.0] = VRFMultiplierOption(multiplier: 100.0, probability: 100.0)
116 }
117
118 access(all) fun advanceEpoch() {
119 self.epochs[VRFExtension.currentEpoch]!.finalize()
120 VRFExtension.currentEpoch = VRFExtension.currentEpoch + 1
121 VRFExtension.lastEpochStart = getCurrentBlock().timestamp
122
123 self.epochs[VRFExtension.currentEpoch] = EpochData(
124 epochNumber: VRFExtension.currentEpoch,
125 startTime: VRFExtension.lastEpochStart
126 )
127
128 emit EpochAdvanced(epochNumber: VRFExtension.currentEpoch, startTime: VRFExtension.lastEpochStart, yieldPool: VRFExtension.totalYieldPool)
129 }
130
131 access(all) fun addYield(amount: UFix64) {
132 pre {
133 amount > 0.0: "Amount must be positive"
134 }
135
136 VRFExtension.totalYieldPool = VRFExtension.totalYieldPool + amount
137 self.epochs[VRFExtension.currentEpoch]!.addYield(amount: amount)
138 emit YieldAdded(amount: amount, epoch: VRFExtension.currentEpoch)
139 }
140
141 access(all) fun setUserVRFMultiplier(user: Address, multiplier: UFix64) {
142 pre {
143 self.vrfMultipliers[multiplier] != nil: "Invalid multiplier"
144 }
145
146 let vault = self.vaultCap.borrow() ?? panic("Cannot borrow vault")
147 vault.setUserVRFMultiplier(user: user, multiplier: multiplier)
148 emit VRFMultiplierSet(user: user, multiplier: multiplier)
149 }
150
151 access(all) fun recordUserDeposit(user: Address, amount: UFix64, vrfMultiplier: UFix64) {
152 let eligibilityEpoch = VRFExtension.currentEpoch + VRFExtension.epochsPerPayout
153
154 if self.userEpochData[user] == nil {
155 self.userEpochData[user] = {}
156 }
157
158 if self.userEpochData[user]![eligibilityEpoch] == nil {
159 self.userEpochData[user]!.insert(key: eligibilityEpoch, UserEpochData(
160 user: user,
161 epoch: eligibilityEpoch,
162 vrfMultiplier: vrfMultiplier,
163 depositAmount: amount,
164 claimed: false,
165 won: false,
166 payoutAmount: 0.0
167 ))
168
169 if self.epochs[eligibilityEpoch] == nil {
170 self.epochs[eligibilityEpoch] = EpochData(
171 epochNumber: eligibilityEpoch,
172 startTime: VRFExtension.lastEpochStart + (UFix64(eligibilityEpoch - VRFExtension.currentEpoch) * VRFExtension.epochDuration)
173 )
174 }
175
176 self.epochs[eligibilityEpoch]!.incrementParticipants()
177 } else {
178 let existing = self.userEpochData[user]![eligibilityEpoch]!
179 self.userEpochData[user]!.insert(key: eligibilityEpoch, UserEpochData(
180 user: existing.user,
181 epoch: existing.epoch,
182 vrfMultiplier: existing.vrfMultiplier,
183 depositAmount: existing.depositAmount + amount,
184 claimed: existing.claimed,
185 won: existing.won,
186 payoutAmount: existing.payoutAmount
187 ))
188 }
189 }
190
191 access(all) fun claimReward(user: Address, epochNumber: UInt64): {String: UFix64} {
192 pre {
193 epochNumber < VRFExtension.currentEpoch: "Epoch not completed"
194 epochNumber % VRFExtension.epochsPerPayout == 0: "Not a payout epoch"
195 self.userEpochData[user] != nil: "User not found"
196 self.userEpochData[user]![epochNumber] != nil: "Not eligible for this epoch"
197 !self.userEpochData[user]![epochNumber]!.claimed: "Already claimed"
198 }
199
200 let vault = self.vaultCap.borrow() ?? panic("Cannot borrow vault")
201 let userPosition = vault.getUserPosition(user: user) ?? panic("No user position")
202
203 assert(userPosition.yieldEligible, message: "Not yield eligible")
204
205 let userData = self.userEpochData[user]![epochNumber]!
206 let epochData = self.epochs[epochNumber]!
207
208 let baseYield = epochData.participantCount > 0
209 ? epochData.totalYieldPool / UFix64(epochData.participantCount)
210 : 0.0
211
212 let multiplierOption = self.vrfMultipliers[userData.vrfMultiplier]!
213
214 let blockHeight = getCurrentBlock().height
215 let timestamp = getCurrentBlock().timestamp
216 let randomSeed = UInt64(blockHeight) * 1000000 + UInt64(timestamp)
217 let normalizedRandom = randomSeed % UInt64(VRFExtension.BASIS_POINTS)
218 let won = UFix64(normalizedRandom) < multiplierOption.probability
219
220 var actualPayout = 0.0
221
222 if won {
223 let potentialPayout = baseYield * multiplierOption.multiplier
224 let availableYield = epochData.totalYieldPool - epochData.totalDistributed
225
226 actualPayout = potentialPayout < availableYield ? potentialPayout : availableYield
227
228 if actualPayout > 0.0 {
229 self.epochs[epochNumber]!.recordDistribution(amount: actualPayout)
230 VRFExtension.totalYieldPool = VRFExtension.totalYieldPool - actualPayout
231 VRFExtension.totalDistributed = VRFExtension.totalDistributed + actualPayout
232 }
233 }
234
235 self.userEpochData[user]!.insert(key: epochNumber, UserEpochData(
236 user: userData.user,
237 epoch: userData.epoch,
238 vrfMultiplier: userData.vrfMultiplier,
239 depositAmount: userData.depositAmount,
240 claimed: true,
241 won: won,
242 payoutAmount: actualPayout
243 ))
244
245 emit RewardClaimed(user: user, epoch: epochNumber, won: won, baseYield: baseYield, actualPayout: actualPayout, multiplier: userData.vrfMultiplier)
246
247 return {
248 "won": won ? 1.0 : 0.0,
249 "baseYield": baseYield,
250 "actualPayout": actualPayout,
251 "multiplier": userData.vrfMultiplier,
252 "winProbability": multiplierOption.probability
253 }
254 }
255
256 access(all) fun getEpochInfo(epochNumber: UInt64): EpochData? {
257 return self.epochs[epochNumber]
258 }
259
260 access(all) fun getUserEpochData(user: Address, epochNumber: UInt64): UserEpochData? {
261 if self.userEpochData[user] == nil {
262 return nil
263 }
264 return self.userEpochData[user]![epochNumber]
265 }
266
267 access(all) fun getClaimableEpochs(user: Address): [UInt64] {
268 let claimable: [UInt64] = []
269
270 if self.userEpochData[user] == nil {
271 return claimable
272 }
273
274 var epoch = VRFExtension.epochsPerPayout
275 while epoch < VRFExtension.currentEpoch {
276 if self.userEpochData[user]![epoch] != nil && !self.userEpochData[user]![epoch]!.claimed {
277 claimable.append(epoch)
278 }
279 epoch = epoch + VRFExtension.epochsPerPayout
280 }
281
282 return claimable
283 }
284
285 access(all) fun getAvailableMultipliers(): [VRFMultiplierOption] {
286 return [
287 self.vrfMultipliers[1.0]!,
288 self.vrfMultipliers[2.0]!,
289 self.vrfMultipliers[5.0]!,
290 self.vrfMultipliers[10.0]!,
291 self.vrfMultipliers[50.0]!,
292 self.vrfMultipliers[100.0]!
293 ]
294 }
295
296 access(all) fun getCurrentEpochStatus(): {String: AnyStruct} {
297 let endTime = VRFExtension.lastEpochStart + VRFExtension.epochDuration
298 let timeRemaining = getCurrentBlock().timestamp < endTime ? endTime - getCurrentBlock().timestamp : 0.0
299
300 return {
301 "currentEpoch": VRFExtension.currentEpoch,
302 "timeRemaining": timeRemaining,
303 "yieldPool": VRFExtension.totalYieldPool,
304 "totalDistributed": VRFExtension.totalDistributed,
305 "epochDuration": VRFExtension.epochDuration,
306 "epochsPerPayout": VRFExtension.epochsPerPayout
307 }
308 }
309 }
310
311 access(all) fun createExtension(vaultCap: Capability<&VaultCore.Vault>): @Extension {
312 return <- create Extension(vaultCap: vaultCap)
313 }
314
315 access(all) fun getMetrics(): {String: AnyStruct} {
316 return {
317 "currentEpoch": self.currentEpoch,
318 "epochDuration": self.epochDuration,
319 "epochsPerPayout": self.epochsPerPayout,
320 "totalYieldPool": self.totalYieldPool,
321 "totalDistributed": self.totalDistributed
322 }
323 }
324
325 init() {
326 self.ExtensionStoragePath = /storage/VRFExtension
327 self.ExtensionPublicPath = /public/VRFExtension
328 self.CADENCE_ARCH_VRF = 0xe467b9dd11fa00df
329 self.BASIS_POINTS = 10000.0
330 self.currentEpoch = 1
331 self.epochDuration = 604800.0
332 self.lastEpochStart = getCurrentBlock().timestamp
333 self.epochsPerPayout = 4
334 self.totalYieldPool = 0.0
335 self.totalDistributed = 0.0
336 }
337}