Smart Contract
Teleport
A.e81193c424cfd3fb.Teleport
1
2import NonFungibleToken from 0x1d7e57aa55817448
3import Clock from 0xe81193c424cfd3fb
4import Wearables from 0xe81193c424cfd3fb
5import FindUtils from 0x097bafa4e0b48eef
6import FindRelatedAccounts from 0x097bafa4e0b48eef
7import FlowToken from 0x1654653399040a61
8import FungibleToken from 0xf233dcee88fe0abe
9import TokenForwarding from 0xe544175ee0461c4b
10// import "DapperStorageRent"
11import GenesisBoxRegistry from 0xe81193c424cfd3fb
12
13
14access(all) contract Teleport {
15
16 //mapping of remoteId so DoodleId to data
17 access(contract) let registryRemote: {UInt64: Data}
18
19 //mapping of dooplicator id to doodleId
20 access(contract) let registryTeleporter: {UInt64: UInt64}
21
22 //was misspelled in an earlier version, needs to be here...
23 access(all) event Refueled()
24 access(all) event Refuelled(address:Address, amount:UFix64, missingStorage:UInt64, storageUsed:UInt64, storageCapacity:UInt64)
25
26 access(all) event Teleported(data:Data)
27 access(all) event Opened(data:GenesisBoxRegistry.Data)
28
29 access(all) struct Data {
30
31 //the receiver address on flow
32 access(all) let receiver: Address
33
34 //the doodleId of the asset beeing teleported
35 access(all) let remoteId:UInt64
36
37 //the id of the teleporter token
38 access(all) let teleporterId:UInt64
39
40 //the remote address on ETH
41 access(all) let remoteAddress:String
42
43 //context like ethTx or other info to add to event
44 access(all) let context: {String:String}
45
46 //the wearable template ids that will be minted and sent
47 access(all) let wearableTemplateIds:[UInt64]
48
49
50 init(receiver: Address,remoteId:UInt64,remoteAddress:String, templateId: UInt64, wearableTemplateIds:[UInt64],context: {String:String}, teleporterId:UInt64) {
51 self.receiver=receiver
52 self.remoteId=remoteId
53 self.remoteAddress=remoteAddress
54 self.context=context
55 self.wearableTemplateIds=wearableTemplateIds
56 self.teleporterId=teleporterId
57 }
58 }
59
60 //store the proxy for the admin
61 access(all) let TeleportProxyPublicPath: PublicPath
62 access(all) let TeleportProxyStoragePath: StoragePath
63 access(all) let TeleportServerStoragePath: StoragePath
64
65
66 // This is just an empty resource to signal that you can control the admin, more logic can be added here or changed later if you want to
67 access(all) resource Server {
68
69 }
70
71 /// ===================================================================================
72 // Teleport things
73 /// ===================================================================================
74
75 //Teleport client to use for capability receiver pattern
76 access(all) fun createTeleportProxyClient() : @TeleportProxy {
77 return <- create TeleportProxy()
78 }
79
80 //interface to use for capability receiver pattern
81 access(all) resource interface TeleportProxyClient {
82 access(all) fun addCapability(_ cap: Capability<&Server>)
83 }
84
85 // Entitlement for TeleportProxy Owner
86 access(all) entitlement Owner
87
88 //admin proxy with capability receiver
89 access(all) resource TeleportProxy: TeleportProxyClient {
90
91 access(self) var capability: Capability<&Server>?
92
93 access(all) fun addCapability(_ cap: Capability<&Server>) {
94 pre {
95 cap.check() : "Invalid server capablity"
96 self.capability == nil : "Server already set"
97 }
98 self.capability = cap
99 }
100
101 access(Owner) fun teleport(_ data:Data) {
102 pre {
103 self.capability != nil: "Cannot create Teleport, capability is not set"
104 }
105
106 let status = Teleport.isAllowed(remoteId: data.remoteId, teleporterId: data.teleporterId)
107 if !status.allowed {
108 panic(status.message)
109 }
110
111 let trust = Teleport.isTrusted(flow: data.receiver, ethereum: data.remoteAddress)
112 if !trust.allowed{
113 panic(trust.message)
114 }
115
116
117 let account=getAccount(data.receiver)
118 let wearable= account.capabilities.get<&Wearables.Collection>(Wearables.CollectionPublicPath)!.borrow() ?? panic("cannot borrow werable cap")
119
120 let context=data.context
121 for id in data.wearableTemplateIds {
122 Wearables.mintNFT(recipient: wearable, template:id, context:context)
123 }
124
125 Teleport.registryRemote[data.remoteId]=data
126 Teleport.registryTeleporter[data.teleporterId]=data.remoteId
127
128 emit Teleported(data:data)
129
130 if let receiver= account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!.borrow() {
131 let isDapper=receiver.isInstance(Type<@TokenForwarding.Forwarder>())
132
133 if !isDapper {
134 return
135 }
136 }
137
138 //try to refill first and then fill up if we need to?
139 // DapperStorageRent.tryRefill(data.receiver)
140
141 let buffer=1000 as UInt64
142
143 let recipient=getAccount(data.receiver)
144 var used: UInt64 = recipient.storage.used
145 var capacity: UInt64 = recipient.storage.capacity
146
147
148 var missingStorage=0 as UInt64
149 if used > capacity {
150 missingStorage=(used-capacity)+buffer
151 } else {
152 let remainingStorage=capacity-used
153 if remainingStorage < buffer{
154 missingStorage=buffer-remainingStorage
155 }
156 }
157
158 // if missingStorage > 0 {
159 // let amount=0.1 //we just give a fixed amount right now
160 // emit Refuelled(address:data.receiver, amount:amount, missingStorage:missingStorage, storageUsed:used, storageCapacity:capacity)
161 // let vaultRef = Teleport.account.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) ?? panic("cannot get flow token vault")
162 // let newVault <- DapperStorageRent.fundedRefillV2(address: data.receiver, tokens: <- vaultRef.withdraw(amount: amount))
163 // vaultRef.deposit(from: <-newVault)
164 // }
165 }
166
167 access(Owner) fun openBox(_ data:GenesisBoxRegistry.Data) {
168 pre {
169 self.capability != nil: "Cannot create Teleport, capability is not set"
170 }
171
172 let record = GenesisBoxRegistry.getGenesisBoxStatus(data.genesisBoxId)
173 if record != nil {
174 panic("Genesis Box ID : ".concat(data.genesisBoxId.toString()).concat(" has already been opened by ").concat(record!.receiver.toString()))
175 }
176
177 let trust = Teleport.isTrusted(flow: data.receiver, ethereum: data.remoteAddress)
178 if !trust.allowed{
179 panic(trust.message)
180 }
181
182
183 let account=getAccount(data.receiver)
184 let wearable= account.capabilities.get<&Wearables.Collection>(Wearables.CollectionPublicPath)!.borrow() ?? panic("cannot borrow werable cap")
185
186 let context=data.context
187 for id in data.wearableTemplateIds {
188 Wearables.mintNFT(recipient: wearable, template:id, context:context)
189 }
190
191 GenesisBoxRegistry.setData(data)
192
193 emit Opened(data:data)
194
195 if let receiver= account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!.borrow() {
196 let isDapper=receiver.isInstance(Type<@TokenForwarding.Forwarder>())
197
198 if !isDapper {
199 return
200 }
201 }
202
203 // //try to refill first and then fill up if we need to?
204 // DapperStorageRent.tryRefill(data.receiver)
205
206 let buffer=1000 as UInt64
207
208 let recipient=getAccount(data.receiver)
209 var used: UInt64 = recipient.storage.used
210 var capacity: UInt64 = recipient.storage.capacity
211
212
213 var missingStorage=0 as UInt64
214 if used > capacity {
215 missingStorage=(used-capacity)+buffer
216 } else {
217 let remainingStorage=capacity-used
218 if remainingStorage < buffer{
219 missingStorage=buffer-remainingStorage
220 }
221 }
222
223 // if missingStorage > 0 {
224 // let amount=0.1 //we just give a fixed amount right now
225 // emit Refuelled(address:data.receiver, amount:amount, missingStorage:missingStorage, storageUsed:used, storageCapacity:capacity)
226 // let vaultRef = Teleport.account.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) ?? panic("cannot get flow token vault")
227 // let newVault <- DapperStorageRent.fundedRefillV2(address: data.receiver, tokens: <- vaultRef.withdraw(amount: amount))
228 // vaultRef.deposit(from: <-newVault)
229 // }
230 }
231
232 init() {
233 self.capability = nil
234 }
235 }
236
237 access(all) fun getRemoteStatus(_ id:UInt64) : Data? {
238 return self.registryRemote[id]
239 }
240
241 access(all) fun getTeleporterStatus(_ id:UInt64) : Data? {
242 if let remoteId = self.registryTeleporter[id] {
243 return self.getRemoteStatus(remoteId)
244 }
245 return nil
246 }
247
248 access(all) fun getGenesisBoxStatus(_ id:UInt64) : GenesisBoxRegistry.Data? {
249 return GenesisBoxRegistry.getGenesisBoxStatus(id)
250 }
251
252 access(all) fun isValid(remoteId: UInt64, teleporterId: UInt64) : Bool {
253 if let checkRemoteId = self.registryTeleporter[teleporterId] {
254 return remoteId == checkRemoteId
255 }
256 return false
257 }
258
259 access(all) struct AllowedStatus {
260 access(all) let allowed: Bool
261 access(all) let message: String
262
263 init(remoteId: UInt64, teleporterId: UInt64) {
264 var remoteMessage = ""
265 var teleporterMessage = ""
266 let ids = "DoodleID : ".concat(remoteId.toString()).concat(" DooplicatorID : ").concat(teleporterId.toString())
267 if let remote = Teleport.getRemoteStatus(remoteId) {
268 remoteMessage = FindUtils.joinMapToString(remote.context)
269 }
270 if let teleporter = Teleport.getTeleporterStatus(teleporterId) {
271 teleporterMessage = FindUtils.joinMapToString(teleporter.context)
272 }
273
274 if remoteMessage == "" && teleporterMessage == "" {
275 self.message = ""
276 self.allowed = true
277
278 } else if remoteMessage == teleporterMessage {
279 self.message = ids.concat(" was teleported as a combination. ").concat(remoteMessage)
280 self.allowed = false
281 } else {
282 var message = ""
283 var remoteTeleported = false
284 message = message.concat("DoodleID : ").concat(remoteId.toString())
285 if remoteMessage != "" {
286 message = message.concat(" was teleported. Remote : ").concat(remoteMessage)
287 remoteTeleported = true
288 } else {
289 message = message.concat(" was not teleported. ")
290 }
291
292 message = message.concat("DooplicatorID : ").concat(teleporterId.toString())
293 if teleporterMessage != "" {
294 message = message.concat(" was teleported. Teleporter : ").concat(teleporterMessage)
295 if remoteTeleported {
296 message = "Combination was teleported separately. ".concat(message)
297 }
298 } else {
299 message = message.concat(" was not teleported. ")
300 }
301 self.message = message
302 self.allowed = false
303 }
304 }
305 }
306
307 access(all) struct TrustStatus{
308 access(all) let allowed:Bool
309 access(all) let message:String
310
311 init(flow:Address, ethereum:String) {
312 let account=getAccount(flow)
313 if let related = account.capabilities.get<&FindRelatedAccounts.Accounts>(FindRelatedAccounts.publicPath)!.borrow() {
314 if !related.verify(network: "Ethereum", address:ethereum) {
315 self.allowed=false
316 self.message="receiver with address ".concat(flow.toString()).concat(" is not set up to teleport from ETH address ").concat(ethereum)
317 } else {
318 self.allowed=true
319 self.message=""
320 }
321 } else {
322 self.allowed=false
323 self.message="No related accounts registered for the account ".concat(flow.toString())
324 }
325
326 }
327 }
328
329
330 access(all) fun isAllowed(remoteId: UInt64, teleporterId: UInt64) : AllowedStatus {
331 return AllowedStatus(remoteId: remoteId, teleporterId: teleporterId)
332 }
333
334 access(all) fun isTrusted(flow:Address, ethereum: String) : TrustStatus{
335 return TrustStatus(flow:flow, ethereum:ethereum)
336 }
337
338 access(all) fun isGenesisBoxAllowed(_ genesisBoxId: UInt64) : GenesisBoxRegistry.AllowedStatus {
339 return GenesisBoxRegistry.AllowedStatus(genesisBoxId)
340 }
341
342 init() {
343
344 self.TeleportProxyPublicPath= /public/teleportProxy
345 self.TeleportProxyStoragePath=/storage/teleportProxy
346 self.registryRemote={}
347 self.registryTeleporter={}
348
349 //create a dummy server for now, if we have a resource later we want to use instead of server we can change to that
350 self.TeleportServerStoragePath=/storage/teleportServer
351 self.account.storage.save(<- create Server(), to: self.TeleportServerStoragePath)
352 let cap = self.account.capabilities.storage.issue<&Server>(self.TeleportServerStoragePath)
353 }
354}