Smart Contract

FlowContractAudits

A.e467b9dd11fa00df.FlowContractAudits

Deployed

1w ago
Feb 19, 2026, 08:39:34 AM UTC

Dependents

0 imports
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