state.gno
11.03 Kb · 277 lines
1package manager
2
3import "gno.land/p/nt/ufmt/v0"
4
5type State struct {
6 Roles map[RoleId]*RoleConfig
7 Targets map[string]*TargetConfig
8}
9
10func NewState() State {
11 return State{
12 Roles: make(map[RoleId]*RoleConfig),
13 Targets: make(map[string]*TargetConfig),
14 }
15}
16
17// =================================================== GETTERS ====================================================
18
19// CanCall reports whether caller can call target selector immediately.
20// This Gno port returns only immediate authorization because scheduled
21// execution and execution delay are intentionally not ported.
22// OpenZeppelin reference:
23// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L140-L156
24func (state *State) CanCall(caller address, target string, selector Selector) CanCallResult {
25 if state.IsTargetClosed(target) {
26 return NewCanCallResult(false)
27 }
28
29 role := state.GetTargetFunctionRole(target, selector)
30
31 membership := state.HasRole(role, caller)
32 if !membership.IsMember {
33 return NewCanCallResult(false)
34 }
35
36 return NewCanCallResult(true)
37}
38
39// IsAuthorized is a boolean convenience wrapper around CanCall.
40// OpenZeppelin reference:
41// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L140-L156
42func (state *State) IsAuthorized(caller address, target string, selector Selector) bool {
43 return state.CanCall(caller, target, selector).Immediate
44}
45
46// HasRole reports whether account currently has an active role membership.
47// This Gno port returns membership only because execution delay is not ported.
48// OpenZeppelin reference:
49// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L211-L220
50func (state *State) HasRole(role RoleId, account address) HasRoleResult {
51 if role == PublicRole {
52 return NewHasRoleResult(true)
53 }
54
55 roleConfig := state.getRole(role)
56 if roleConfig == nil {
57 return NewHasRoleResult(false)
58 }
59
60 return NewHasRoleResult(roleConfig.hasMember(account, CurrentTimePoint()))
61}
62
63// GetRoleAdmin returns the admin role for role, defaulting to AdminRole.
64// OpenZeppelin reference:
65// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L183-L186
66func (state *State) GetRoleAdmin(role RoleId) RoleId {
67 roleConfig := state.getRole(role)
68 if roleConfig == nil {
69 return AdminRole
70 }
71
72 return roleConfig.adminRole()
73}
74
75// GetRoleGrantDelay returns the grant delay configured for role.
76// OpenZeppelin reference:
77// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L193-L196
78func (state *State) GetRoleGrantDelay(role RoleId) Delay {
79 roleConfig := state.getRole(role)
80 if roleConfig == nil {
81 return 0
82 }
83
84 return roleConfig.grantDelay()
85}
86
87// GetTargetFunctionRole returns the role required for target selector.
88// Unconfigured selectors default to AdminRole in this Gno representation.
89// OpenZeppelin reference:
90// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L173-L176
91func (state *State) GetTargetFunctionRole(target string, selector Selector) RoleId {
92 targetConfig := state.getTarget(target)
93 if targetConfig == nil {
94 return AdminRole
95 }
96
97 return targetConfig.functionRole(selector)
98}
99
100// IsTargetClosed reports whether target is closed.
101// OpenZeppelin reference:
102// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L168-L171
103func (state *State) IsTargetClosed(target string) bool {
104 targetConfig := state.getTarget(target)
105
106 return targetConfig != nil && targetConfig.isClosed()
107}
108
109// =============================================== ROLE MANAGEMENT ===============================================
110
111// GrantRole grants role membership to account and returns true for a new member.
112// This Gno port retains grant delay but intentionally omits OZ execution delay.
113// OpenZeppelin references:
114// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L233-L236
115// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L271-L298
116func (state *State) GrantRole(role RoleId, account address) bool {
117 if role == PublicRole {
118 panic(errorWithDetails(errAccessManagerLockedRole, ufmt.Sprintf("role %d is locked", role)))
119 }
120
121 state.ensureRole(role)
122
123 return state.getRole(role).grant(account, CurrentTimePoint())
124}
125
126// RevokeRole revokes role membership from account and reports whether it existed.
127// OpenZeppelin references:
128// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L238-L240
129// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L300-L319
130func (state *State) RevokeRole(role RoleId, account address) bool {
131 if role == PublicRole {
132 panic(errorWithDetails(errAccessManagerLockedRole, ufmt.Sprintf("role %d is locked", role)))
133 }
134
135 state.ensureRole(role)
136
137 if role == AdminRole && state.isLastAdmin(account) {
138 panic(errAccessManagerLastAdmin)
139 }
140
141 return state.getRole(role).revoke(account)
142}
143
144// isLastAdmin reports whether account is the only currently active AdminRole
145// member. Prevents bricking the system by removing the final admin.
146func (state *State) isLastAdmin(account address) bool {
147 adminRole := state.getRole(AdminRole)
148 if adminRole == nil {
149 return false
150 }
151
152 now := CurrentTimePoint()
153 if !adminRole.hasMember(account, now) {
154 return false
155 }
156
157 for member, access := range adminRole.Members {
158 if member != account && access.isActiveAt(now) {
159 return false
160 }
161 }
162
163 return true
164}
165
166// RenounceRole revokes caller-confirmed role membership from account.
167// OpenZeppelin reference:
168// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L243-L249
169func (state *State) RenounceRole(role RoleId, account address, callerConfirmation address) bool {
170 if account != callerConfirmation {
171 panic(errAccessManagerBadConfirmation)
172 }
173
174 return state.RevokeRole(role, account)
175}
176
177// SetRoleAdmin sets the admin role that can grant or revoke role.
178// OpenZeppelin references:
179// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L251-L254
180// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L321-L337
181func (state *State) SetRoleAdmin(role RoleId, admin RoleId) {
182 RequireUnlockedConfigRole(role)
183
184 state.ensureRole(role)
185 state.getRole(role).setAdmin(admin)
186}
187
188// SetGrantDelay sets the delay before future grants of role become active.
189// OpenZeppelin references:
190// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L261-L264
191// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L357-L371
192func (state *State) SetGrantDelay(role RoleId, delay Delay) {
193 RequireUnlockedConfigRole(role)
194
195 state.ensureRole(role)
196 state.getRole(role).setGrantDelay(delay)
197}
198
199// ============================================= FUNCTION MANAGEMENT ==============================================
200
201// SetTargetFunctionRole sets the role required for target selector.
202// OpenZeppelin reference:
203// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L385-L393
204func (state *State) SetTargetFunctionRole(target string, selector Selector, role RoleId) {
205 state.ensureTarget(target)
206 state.getTarget(target).setFunctionRole(selector, role)
207}
208
209// SetTargetFunctionRoles sets the same role for multiple target selectors.
210// OpenZeppelin reference:
211// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L373-L383
212func (state *State) SetTargetFunctionRoles(target string, selectors []Selector, role RoleId) {
213 state.ensureTarget(target)
214 state.getTarget(target).setFunctionRoles(selectors, role)
215}
216
217// =============================================== MODE MANAGEMENT ================================================
218
219// SetTargetClosed sets whether target rejects calls regardless of selector role.
220// OpenZeppelin references:
221// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L412-L416
222// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L418-L426
223func (state *State) SetTargetClosed(target string, closed bool) {
224 state.ensureTarget(target)
225 state.getTarget(target).setClosed(closed)
226}
227
228// ================================================= ADMIN LOGIC ==================================================
229
230// CanAdminRole reports whether caller has the admin role required for role.
231// OpenZeppelin reference:
232// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L652-L657
233func (state *State) CanAdminRole(role RoleId, caller address) CanCallResult {
234 adminRole := state.GetRoleAdmin(role)
235
236 membership := state.HasRole(adminRole, caller)
237 if !membership.IsMember {
238 return NewCanCallResult(false)
239 }
240
241 return NewCanCallResult(true)
242}
243
244// CanManageTarget reports whether caller can manage target configuration.
245// OpenZeppelin references:
246// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L101-L105
247// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L640-L650
248func (state *State) CanManageTarget(caller address) CanCallResult {
249 membership := state.HasRole(AdminRole, caller)
250 if !membership.IsMember {
251 return NewCanCallResult(false)
252 }
253
254 return NewCanCallResult(true)
255}
256
257func (state *State) getRole(role RoleId) *RoleConfig {
258 return state.Roles[role]
259}
260
261func (state *State) getTarget(target string) *TargetConfig {
262 return state.Targets[target]
263}
264
265func (state *State) ensureRole(role RoleId) {
266 roleConfig, found := state.Roles[role]
267 if !found || roleConfig == nil {
268 state.Roles[role] = NewRoleConfig(AdminRole, 0)
269 }
270}
271
272func (state *State) ensureTarget(target string) {
273 targetConfig, found := state.Targets[target]
274 if !found || targetConfig == nil {
275 state.Targets[target] = NewTargetConfig(false)
276 }
277}