Smart Contract
DelayedTransfer
A.08dd120226ec2213.DelayedTransfer
1import FungibleToken from 0xf233dcee88fe0abe
2
3access(all) contract DelayedTransfer {
4 // path for admin resource
5 access(all) let AdminPath: StoragePath
6 // ========== events ==========
7 access(all) event DelayedTransferAdded(id: String)
8 access(all) event DelayedTransferExecuted(
9 id: String,
10 receiver: Address,
11 token: String,
12 amount: UFix64
13 )
14 access(all) event DelayPeriodUpdated(period: UInt64)
15
16 // ========== structs ==========
17 /// one delayed transfer info, will release if current block.timestamp >= info.blockTs + delayPeriod
18 access(all) struct Info {
19 // block.timestamp when this xfer is added
20 access(all) let blockTs: UFix64
21 // .borrow then .deposit
22 access(all) let receiverCap: Capability<&{FungibleToken.Receiver}>
23
24 init(blockTs: UFix64, receiverCap: Capability<&{FungibleToken.Receiver}>){
25 self.blockTs = blockTs
26 self.receiverCap = receiverCap
27 }
28 }
29
30 // ========== contract states and maps ==========
31 // how many seconds for delayed transfer to wait,
32 // default is 3600*24 = 86400, can be changed by Admin
33 access(all) var delayPeriod: UInt64
34 // map from unique ID string to Info struct and from vault, have to keep fromVault separate as struct can't include Resource
35 // and Capability can only be acquired by path which doesn't exist in mint case
36 access(account) var infoMap: {String: Info}
37 access(account) var vaultMap: @{String: {FungibleToken.Vault}}
38 // when SafeBox/PegBridge is paused, this will also be paused
39 access(all) var isPaused: Bool
40
41 // ========== resource ==========
42 access(all) resource Admin {
43 access(all) fun setDelayPeriod(newP: UInt64) {
44 DelayedTransfer.delayPeriod = newP
45 emit DelayPeriodUpdated(
46 period: newP
47 )
48 }
49 // createNewAdmin creates a new Admin resource
50 access(all) fun createNewAdmin(): @Admin {
51 return <-create Admin()
52 }
53 }
54
55 // ========== functions ==========
56 init() {
57 self.delayPeriod = 86400
58 self.infoMap = {}
59 self.vaultMap <- {}
60 self.isPaused = false
61 self.AdminPath = /storage/DelayedTransferAdmin
62 self.account.storage.save<@Admin>(<- create Admin(), to: self.AdminPath)
63 }
64
65 access(account) fun pause() {
66 self.isPaused = true
67 }
68 access(account) fun unPause() {
69 self.isPaused = false
70 }
71
72 // only accessible by contracts in the same account to avoid spam (storage cost and valid ids)
73 access(account) fun addDelayXfer(id: String, receiverCap: Capability<&{FungibleToken.Receiver}>, from: @{FungibleToken.Vault}) {
74 pre {
75 !self.infoMap.containsKey(id): "id already exists!"
76 !self.vaultMap.containsKey(id): "id already exists!"
77 }
78 self.infoMap[id] = Info(blockTs: getCurrentBlock().timestamp, receiverCap: receiverCap)
79 let old <- self.vaultMap[id] <- from
80 destroy old
81 emit DelayedTransferAdded(id: id)
82 }
83
84 // execute xfer, if id is found in infoMap and block is big enough, deposit fund to receiver
85 // we must restrict to account because if safebox/pegbridge contracts are paused, this can't be called either
86 // note if only one contract is paused, we pause all executeDelayXfer for safety
87 access(account) fun executeDelayXfer(_ wdId: String) {
88 pre {
89 !self.isPaused: "delay transfer is paused"
90 self.infoMap.containsKey(wdId): "wdId not found!"
91 self.vaultMap.containsKey(wdId): "wdId not found!"
92 }
93 let xfer = self.infoMap[wdId]!
94 assert(getCurrentBlock().timestamp > xfer.blockTs+UFix64(self.delayPeriod), message: "delayed transfer still locked")
95 // delete from map so no re-trigger
96 self.infoMap.remove(key: wdId)
97 let recRef = xfer.receiverCap.borrow() ?? panic("Could not borrow reference to receiver")
98 let fromVault <- self.vaultMap.remove(key: wdId)!
99 let tokStr = fromVault.getType().identifier
100 let amt = fromVault.balance
101 recRef.deposit(from: <- fromVault)
102 emit DelayedTransferExecuted(
103 id: wdId,
104 receiver: recRef.owner!.address,
105 token: tokStr,
106 amount: amt
107 )
108 }
109
110 access(all) view fun delayTransferExist(id: String): Bool {
111 return self.infoMap.containsKey(id)
112 }
113
114 access(all) view fun getDelayBlockTs(id: String): UFix64 {
115 let info = self.infoMap[id] ?? panic("token not support in contract")
116 return info.blockTs
117 }
118}