Smart Contract
FastBreakEscrow
A.254b32edc33e5bc3.FastBreakEscrow
1import aiSportsEscrow from 0x4fdb077419808080
2import FlowToken from 0x1654653399040a61
3import FastBreakV1 from 0x0b2a3299cc857e29
4import FungibleToken from 0xf233dcee88fe0abe
5
6access(all) contract FastBreakEscrow {
7
8 access(all) let EscrowAdminStoragePath: StoragePath
9
10 /// A game of Fast Break has the following status transitions
11 access(all) enum EscrowStatus: UInt8 {
12 access(all) case OPEN /// Game is open for submission
13 access(all) case CLOSED /// Game is over and rewards are being distributed
14 }
15
16 access (all) let FBEscrowEntryById: {UInt64 : FastBreakEscrow.FBEscrowEntry}
17 access (all) let UserFBEscrowsByAddress: {Address: [UInt64]}
18 access (contract) var nextFBEscrowEntryId: UInt64
19 access (all) let FBEscrowContestById: {UInt64 : FastBreakEscrow.FBEscrowContest}
20
21 access (all) struct FBEscrowContest {
22 access(all) let escrowID: UInt64
23 access(all) let fastBreakGameId: String
24 access(all) var totalEntries: UInt64
25 access(all) let FBEscrowEntries: [UInt64]
26 access(all) let protocolFees: UFix64
27 access(all) let dues: UFix64
28
29 access(contract) fun addFundingEntries(numEntries: UInt64) {
30 self.totalEntries = self.totalEntries + numEntries
31 }
32
33 access(contract) fun addNewEntry(entryId: UInt64, numEntries: UInt64) {
34 self.totalEntries = self.totalEntries + numEntries
35 self.FBEscrowEntries.append(entryId)
36 }
37
38 init(escrowID: UInt64, fastBreakGameId: String, protocolFees: UFix64, dues: UFix64) {
39 self.escrowID = escrowID
40 self.fastBreakGameId = fastBreakGameId
41 self.totalEntries = 0
42 self.FBEscrowEntries = []
43 self.protocolFees = protocolFees
44 self.dues = dues
45 }
46 }
47
48 //represents an entry to the FBEscrow
49 access(all) struct FBEscrowEntry {
50 access(all) let escrowID: UInt64
51 access(all) var points: UInt64
52 access(all) var win: Bool
53 access(all) var winnings: UFix64
54 access(all) var submittedAt: UInt64
55 access(all) let fastBreakPlayerId: UInt64
56 access(all) var status: FastBreakEscrow.EscrowStatus
57 access(all) let flowAddress: Address
58 access(all) let fbAddress: Address
59 access(all) let numEntries: UInt32
60 access(all) let duesPaid: UFix64
61 access(all) let FBEscrowEntryId: UInt64
62
63 init(escrowID: UInt64, fastBreakPlayerId: UInt64, flowAddress: Address, fbAddress: Address, numEntries: UInt32, duesPaid: UFix64, id: UInt64) {
64 self.escrowID = escrowID
65 self.points = 0
66 self.win = false
67 self.winnings = 0.0
68 self.submittedAt = UInt64(getCurrentBlock().timestamp)
69 self.status = FastBreakEscrow.EscrowStatus.OPEN
70 self.fastBreakPlayerId = fastBreakPlayerId
71 self.flowAddress = flowAddress
72 self.fbAddress = fbAddress
73 self.numEntries = numEntries
74 self.duesPaid = duesPaid
75 self.FBEscrowEntryId = id
76 }
77
78 access(contract) fun setResults(points: UInt64, win: Bool) {
79 self.points = points
80 self.win = win
81 self.status = FastBreakEscrow.EscrowStatus.CLOSED
82 }
83
84 access(contract) fun setWinnings(winnings: UFix64) {
85 self.winnings = winnings
86 }
87
88 access(contract) fun setClosed(points: UInt64) {
89 self.status = FastBreakEscrow.EscrowStatus.CLOSED
90 self.points = points
91 }
92 }
93
94 access (all) fun getUserFBEscrows(userAddress: Address) : [FastBreakEscrow.FBEscrowEntry] {
95 let userEscrowEntries = FastBreakEscrow.UserFBEscrowsByAddress[userAddress] ?? []
96 let userFBEscrows: [FastBreakEscrow.FBEscrowEntry] = []
97
98 for entryId in userEscrowEntries {
99 let entry = FastBreakEscrow.FBEscrowEntryById[entryId] ?? panic("Entry not found")
100 userFBEscrows.append(entry)
101 }
102 return userFBEscrows
103 }
104
105 access(all) fun fundFBEscrow(escrowAddress: Address, escrowID: UInt64, flowTokenVault: @{FungibleToken.Vault}){
106
107 let amount: UFix64 = flowTokenVault.balance
108 assert(UFix64(UInt64(amount)) == amount, message: "Amount must be a whole number")
109
110 let amountUInt64: UInt64 = UInt64(amount)
111
112 let escrowAccount = getAccount(escrowAddress)
113 let escrowRef = escrowAccount.capabilities.borrow<&aiSportsEscrow.EscrowMinter>(aiSportsEscrow.EscrowMinterPublicPath)
114 ?? panic("Resource does not exist.")
115 escrowRef.addFunds(escrowID: escrowID, tokens: <-flowTokenVault)
116
117 let contestsRef: &{UInt64 : FastBreakEscrow.FBEscrowContest} = &FastBreakEscrow.FBEscrowContestById
118 let fastBreakEscrowContestRef = contestsRef[escrowID] ?? panic("No escrow found")
119 fastBreakEscrowContestRef.addFundingEntries(numEntries: amountUInt64)
120 }
121
122
123 access(all) fun joinFBEscrow(escrowAddress: Address, escrowID: UInt64, entries: Int, flowTokenVault: @{FungibleToken.Vault}, signerAddress: Address, fbAddress: Address){
124
125 let escrowAccount = getAccount(escrowAddress) //Hardcode emulator account address - CHANGE TO GLOBAL
126 let escrowRef = escrowAccount.capabilities.borrow<&aiSportsEscrow.Escrow>(aiSportsEscrow.getEscrowPublicPath(id: escrowID)) ?? panic("Could not borrow escrow reference")
127 let leagueDues = escrowRef.leagueDues * UFix64(entries)
128
129 assert(flowTokenVault.balance >= leagueDues, message: "Not enough tokens to join escrow.")
130
131 let minterRef = escrowAccount.capabilities.borrow<&aiSportsEscrow.EscrowMinter>(aiSportsEscrow.EscrowMinterPublicPath)
132 ?? panic("Resource does not exist.")
133
134 minterRef.joinEscrow(escrowID: escrowID, tokens: <- flowTokenVault, sender: signerAddress, nfts: <-[], entries: entries)
135
136 let entryId = FastBreakEscrow.nextFBEscrowEntryId
137 FastBreakEscrow.nextFBEscrowEntryId = entryId + 1
138
139 var fastBreakPlayerId = FastBreakV1.getPlayerIdByAccount(accountAddress: fbAddress)
140
141 let contestsRef: &{UInt64 : FastBreakEscrow.FBEscrowContest} = &FastBreakEscrow.FBEscrowContestById
142 let fastBreakEscrowContestRef = contestsRef[escrowID] ?? panic("No escrow found")
143 fastBreakEscrowContestRef.addNewEntry(entryId: entryId, numEntries: UInt64(entries))
144
145 //get the users submissioin to the FB contest
146 let gameDict = FastBreakV1.getFastBreakGame(id: fastBreakEscrowContestRef.fastBreakGameId) ?? panic("Game does not exist")
147 assert(gameDict.getFastBreakSubmissionByPlayerId(playerId: fastBreakPlayerId) != nil, message: "No submission found for player")
148
149 let fastBreakGameId = fastBreakEscrowContestRef.fastBreakGameId
150
151 let userEntry = FastBreakEscrow.FBEscrowEntry(escrowID: escrowID, fastBreakPlayerId: fastBreakPlayerId, flowAddress: signerAddress, fbAddress: fbAddress, numEntries: UInt32(entries), duesPaid: leagueDues, id: entryId)
152
153 //get users Escrow entries
154 let userEscrowEntries = FastBreakEscrow.UserFBEscrowsByAddress[signerAddress] ?? []
155
156 userEscrowEntries.append(entryId)
157
158 FastBreakEscrow.UserFBEscrowsByAddress.insert(key: signerAddress, userEscrowEntries)
159
160 FastBreakEscrow.FBEscrowEntryById.insert(key: entryId, userEntry)
161
162 }
163
164 access(all) resource FBEscrowAdmin {
165
166 access(all) fun createFBEscrow(escrowID: UInt64, fastBreakGameId: String, protocolFees: UFix64, dues: UFix64) {
167 assert(FastBreakV1.getFastBreakGame(id: fastBreakGameId) != nil, message: "Game does not exist")
168 let newFBEscrowEntry = FastBreakEscrow.FBEscrowContest(escrowID: escrowID, fastBreakGameId: fastBreakGameId, protocolFees: protocolFees, dues: dues)
169 FastBreakEscrow.FBEscrowContestById.insert(key: escrowID, newFBEscrowEntry)
170 }
171
172 access (all) fun closeFBEscrow(escrowAddress: Address, escrowID: UInt64): {Address: UFix64} {
173 let escrow = FastBreakEscrow.FBEscrowContestById[escrowID] ?? panic("No escrow found")
174 let escrowEntries = escrow.FBEscrowEntries
175
176 let escrowAccount = getAccount(escrowAddress) //Hardcode emulator account address - CHANGE TO GLOBAL
177 let escrowRef = escrowAccount.capabilities.borrow<&aiSportsEscrow.Escrow>(aiSportsEscrow.getEscrowPublicPath(id: escrowID)) ?? panic("Could not borrow escrow reference")
178
179 let winners: [UInt64] = []
180 var totalWinningEntries: UInt32 = 0
181
182 let gameDict = FastBreakV1.getFastBreakGame(id: escrow.fastBreakGameId) ?? panic("Game does not exist")
183
184 for entryId in escrowEntries {
185 let entriesRef: &{UInt64 : FastBreakEscrow.FBEscrowEntry} = &FastBreakEscrow.FBEscrowEntryById
186 let entry = entriesRef[entryId] ?? panic("Entry not found")
187
188 //get the users submissioin to the FB contest
189 let submission = gameDict.getFastBreakSubmissionByPlayerId(playerId: entry.fastBreakPlayerId) ?? panic("No submission found for player")
190
191 if submission.win == true {
192 winners.append(entryId)
193 totalWinningEntries = totalWinningEntries + entry.numEntries
194 entry.setResults(points: submission.points, win: submission.win)
195 } else {
196 entry.setClosed(points: submission.points)
197 }
198 }
199
200 let totalDistro = escrowRef.tokens.balance - (escrowRef.tokens.balance * escrow.protocolFees/100.0)
201 let winnerDistro: {Address: UFix64} = {}
202
203 for winner in winners {
204 let entriesRef: &{UInt64:FastBreakEscrow.FBEscrowEntry} = &FastBreakEscrow.FBEscrowEntryById
205 let entry = entriesRef[winner] ?? panic("Entry not found")
206
207 let winnings = totalDistro * (UFix64(entry.numEntries) / UFix64(totalWinningEntries))
208 entry.setWinnings(winnings: winnings)
209
210 winnerDistro[entry.flowAddress] = winnings
211 }
212
213 return winnerDistro
214 }
215
216 }
217
218 init(){
219 self.FBEscrowEntryById = {}
220 self.UserFBEscrowsByAddress = {}
221 self.FBEscrowContestById = {}
222 self.nextFBEscrowEntryId = 1
223
224 // Set the named paths
225 self.EscrowAdminStoragePath = /storage/FBEscrowAdmin
226
227 self.account.storage.save(<-create FBEscrowAdmin(), to: self.EscrowAdminStoragePath)
228 }
229
230}