Smart Contract
FlowContractAudits
A.e467b9dd11fa00df.FlowContractAudits
1pub contract FlowContractAudits {
2
3 // Event that is emitted when a new Auditor resource is created
4 pub event AuditorCreated()
5
6 // Event that is emitted when a new contract audit voucher is created
7 pub event VoucherCreated(address: Address?, recurrent: Bool, expiryBlockHeight: UInt64?, codeHash: String)
8
9 // Event that is emitted when a contract audit voucher is used
10 pub event VoucherUsed(address: Address, key: String, recurrent: Bool, expiryBlockHeight: UInt64?)
11
12 // Event that is emitted when a contract audit voucher is removed
13 pub event VoucherRemoved(key: String, recurrent: Bool, expiryBlockHeight: UInt64?)
14
15 // Dictionary of all vouchers
16 access(contract) var vouchers: {String: AuditVoucher}
17
18 // The storage path for the admin resource
19 pub let AdminStoragePath: StoragePath
20
21 // The storage Path for auditors' AuditorProxy
22 pub let AuditorProxyStoragePath: StoragePath
23
24 // The public path for auditors' AuditorProxy capability
25 pub let AuditorProxyPublicPath: PublicPath
26
27 // Single audit voucher that is used for contract deployment
28 pub struct AuditVoucher {
29
30 // Address of the account the voucher is intended for
31 // If nil, the contract can be deployed to any account
32 pub let address: Address?
33
34 // If false, the voucher will be removed after first use
35 pub let recurrent: Bool
36
37 // If non-nil, the voucher won't be valid after the expiry block height
38 pub let expiryBlockHeight: UInt64?
39
40 // Hash of contract code
41 pub let codeHash: String
42
43 init(address: Address?, recurrent: Bool, expiryBlockHeight: UInt64?, codeHash: String) {
44 self.address = address
45 self.recurrent = recurrent
46 self.expiryBlockHeight = expiryBlockHeight
47 self.codeHash = codeHash
48 }
49 }
50
51 // Returns all current vouchers
52 pub fun getAllVouchers(): {String: AuditVoucher} {
53 return self.vouchers
54 }
55
56 // Get the associated dictionary key for given address and codeHash
57 pub fun generateVoucherKey(address: Address?, codeHash: String): String {
58 if address != nil {
59 return address!.toString().concat("-").concat(codeHash)
60 }
61 return "any-".concat(codeHash)
62 }
63
64 pub fun hashContractCode(_ code: String): String {
65 return String.encodeHex(HashAlgorithm.SHA3_256.hash(code.utf8))
66 }
67
68 // Auditors can create new vouchers and remove them
69 pub resource Auditor {
70
71 // Create new voucher with contract code
72 pub fun addVoucher(address: Address?, recurrent: Bool, expiryOffset: UInt64?, code: String) {
73 let codeHash = FlowContractAudits.hashContractCode(code)
74 self.addVoucherHashed(address: address, recurrent: recurrent, expiryOffset: expiryOffset, codeHash: codeHash)
75 }
76
77 // Create new voucher with hashed contract code
78 pub fun addVoucherHashed(address: Address?, recurrent: Bool, expiryOffset: UInt64?, codeHash: String) {
79
80 // calculate expiry block height based on expiryOffset
81 var expiryBlockHeight: UInt64? = nil
82 if expiryOffset != nil {
83 expiryBlockHeight = getCurrentBlock().height + expiryOffset!
84 }
85
86 let key = FlowContractAudits.generateVoucherKey(address: address, codeHash: codeHash)
87
88 // if a voucher with the same key exists, remove it first
89 FlowContractAudits.deleteVoucher(key)
90
91 let voucher = AuditVoucher(address: address, recurrent: recurrent, expiryBlockHeight: expiryBlockHeight, codeHash: codeHash)
92
93 FlowContractAudits.vouchers.insert(key: key, voucher)
94
95 emit VoucherCreated(address: address, recurrent: recurrent, expiryBlockHeight: expiryBlockHeight, codeHash: codeHash)
96 }
97
98 // Remove a voucher with given key
99 pub fun deleteVoucher(key: String) {
100 FlowContractAudits.deleteVoucher(key)
101 }
102 }
103
104 // Used by admin to set the Auditor capability
105 pub resource interface AuditorProxyPublic {
106 pub fun setAuditorCapability(_ cap: Capability<&Auditor>)
107 }
108
109 // The auditor account will have audit access through AuditorProxy
110 // This enables the admin account to revoke access
111 // See https://docs.onflow.org/cadence/design-patterns/#capability-revocation
112 pub resource AuditorProxy: AuditorProxyPublic {
113 access(self) var auditorCapability: Capability<&Auditor>?
114
115 pub fun setAuditorCapability(_ cap: Capability<&Auditor>) {
116 self.auditorCapability = cap
117 }
118
119 pub fun addVoucher(address: Address?, recurrent: Bool, expiryOffset: UInt64?, code: String) {
120 self.auditorCapability!.borrow()!.addVoucher(address: address, recurrent: recurrent, expiryOffset: expiryOffset, code: code)
121 }
122
123 pub fun addVoucherHashed(address: Address?, recurrent: Bool, expiryOffset: UInt64?, codeHash: String) {
124 self.auditorCapability!.borrow()!.addVoucherHashed(address: address, recurrent: recurrent, expiryOffset: expiryOffset, codeHash: codeHash)
125 }
126
127 pub fun deleteVoucher(key: String) {
128 self.auditorCapability!.borrow()!.deleteVoucher(key: key)
129 }
130
131 init() {
132 self.auditorCapability = nil
133 }
134
135 }
136
137 // Can be called by anyone but needs a capability to function
138 pub fun createAuditorProxy(): @AuditorProxy {
139 return <- create AuditorProxy()
140 }
141
142 pub resource Administrator {
143
144 // Creates new Auditor
145 pub fun createNewAuditor(): @Auditor {
146 emit AuditorCreated()
147 return <-create Auditor()
148 }
149
150 // Checks all vouchers and removes expired ones
151 pub fun cleanupExpiredVouchers() {
152 for key in FlowContractAudits.vouchers.keys {
153 let v = FlowContractAudits.vouchers[key]!
154 if v.expiryBlockHeight != nil {
155 if getCurrentBlock().height > v.expiryBlockHeight! {
156 FlowContractAudits.deleteVoucher(key)
157 }
158 }
159 }
160 }
161
162 // For testing
163 pub fun useVoucherForDeploy(address: Address, code: String): Bool {
164 return FlowContractAudits.useVoucherForDeploy(address: address, code: code)
165 }
166 }
167
168 // This function will be called by the FVM on contract deploy/update
169 access(contract) fun useVoucherForDeploy(address: Address, code: String): Bool {
170 let codeHash = FlowContractAudits.hashContractCode(code)
171 var key = FlowContractAudits.generateVoucherKey(address: address, codeHash: codeHash)
172
173 // first check for voucher based on target account
174 // if not found check for any account
175 if !FlowContractAudits.vouchers.containsKey(key) {
176 key = FlowContractAudits.generateVoucherKey(address: nil, codeHash: codeHash)
177 if !FlowContractAudits.vouchers.containsKey(key) {
178 return false
179 }
180 }
181
182 let v = FlowContractAudits.vouchers[key]!
183
184 // ensure contract code matches the voucher
185 if v.codeHash != codeHash {
186 return false
187 }
188
189 // if expiryBlockHeight is set, check the current block height
190 // and remove/expire the voucher if not within the acceptable range
191 if v.expiryBlockHeight != nil {
192 if getCurrentBlock().height > v.expiryBlockHeight! {
193 FlowContractAudits.deleteVoucher(key)
194 return false
195 }
196 }
197
198 // remove the voucher if not recurrent
199 if !v.recurrent {
200 FlowContractAudits.deleteVoucher(key)
201 }
202
203 emit VoucherUsed(address: address, key: key, recurrent: v.recurrent, expiryBlockHeight: v.expiryBlockHeight)
204 return true
205 }
206
207 // Helper function to remove a voucher with given key
208 access(contract) fun deleteVoucher(_ key: String) {
209 let v = FlowContractAudits.vouchers.remove(key: key)
210 if v != nil {
211 emit VoucherRemoved(key: key, recurrent: v!.recurrent, expiryBlockHeight: v!.expiryBlockHeight)
212 }
213 }
214
215 init() {
216 self.vouchers = {}
217
218 self.AdminStoragePath = /storage/flowContractAuditVouchersAdmin
219 self.AuditorProxyStoragePath = /storage/flowContractAuditVouchersAuditorProxy
220 self.AuditorProxyPublicPath = /public/flowContractAuditVouchersAuditorProxy
221
222 let admin <- create Administrator()
223 self.account.save(<-admin, to: self.AdminStoragePath)
224 }
225}
226