state.gno
12.67 Kb · 313 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// GetRoleMemberSince returns when account's role membership became active,
64// or zero if it is not a member. It corresponds to OZ/Union's since field;
65// execution-delay fields are not ported here.
66//
67// OpenZeppelin: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L205
68// Union: https://github.com/unionlabs/union/blob/8cff0ff34f6baa4cdb1e4650a08985dd05de0c5a/cosmwasm/access-manager/src/contract.rs#L1165
69func (state *State) GetRoleMemberSince(role RoleId, account address) TimePoint {
70 roleConfig := state.getRole(role)
71 if roleConfig == nil {
72 return 0
73 }
74
75 access, found := roleConfig.Members[account]
76 if !found {
77 return 0
78 }
79
80 return access.Since
81}
82
83// GetRoleAdmin returns the admin role for role, defaulting to AdminRole.
84// OpenZeppelin reference:
85// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L183-L186
86func (state *State) GetRoleAdmin(role RoleId) RoleId {
87 roleConfig := state.getRole(role)
88 if roleConfig == nil {
89 return AdminRole
90 }
91
92 return roleConfig.adminRole()
93}
94
95// GetRoleGrantDelay returns the grant delay currently in effect for role. A
96// change scheduled by SetGrantDelay is not reflected until its effect timePoint.
97// OpenZeppelin: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L194-L196
98// Union: https://github.com/unionlabs/union/blob/8cff0ff34f6baa4cdb1e4650a08985dd05de0c5a/cosmwasm/access-manager/src/contract.rs#L1138-L1144
99func (state *State) GetRoleGrantDelay(role RoleId) uint32 {
100 roleConfig := state.getRole(role)
101 if roleConfig == nil {
102 return 0
103 }
104
105 return roleConfig.grantDelay(CurrentTimePoint())
106}
107
108// GetRoleGrantDelayEffect returns when role's scheduled grant delay change
109// takes effect. No OZ/Union equivalent; added to avoid direct field access.
110func (state *State) GetRoleGrantDelayEffect(role RoleId) TimePoint {
111 roleConfig := state.getRole(role)
112 if roleConfig == nil {
113 return 0
114 }
115
116 return roleConfig.GrantDelay.effectDate
117}
118
119// GetTargetFunctionRole returns the role required for target selector.
120// Unconfigured selectors default to AdminRole in this Gno representation.
121// OpenZeppelin reference:
122// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L173-L176
123func (state *State) GetTargetFunctionRole(target string, selector Selector) RoleId {
124 targetConfig := state.getTarget(target)
125 if targetConfig == nil {
126 return AdminRole
127 }
128
129 return targetConfig.functionRole(selector)
130}
131
132// IsTargetClosed reports whether target is closed.
133// OpenZeppelin reference:
134// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L168-L171
135func (state *State) IsTargetClosed(target string) bool {
136 targetConfig := state.getTarget(target)
137
138 return targetConfig != nil && targetConfig.isClosed()
139}
140
141// =============================================== ROLE MANAGEMENT ===============================================
142
143// GrantRole grants role membership to account and returns true for a new member.
144// This Gno port retains grant delay but intentionally omits OZ execution delay.
145// OpenZeppelin references:
146// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L233-L236
147// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L271-L298
148func (state *State) GrantRole(role RoleId, account address) bool {
149 if role == PublicRole {
150 panic(errorWithDetails(errAccessManagerLockedRole, ufmt.Sprintf("role %d is locked", role)))
151 }
152
153 state.ensureRole(role)
154
155 return state.getRole(role).grant(account, CurrentTimePoint())
156}
157
158// RevokeRole revokes role membership from account and reports whether it existed.
159// OpenZeppelin references:
160// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L238-L240
161// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L300-L319
162func (state *State) RevokeRole(role RoleId, account address) bool {
163 if role == PublicRole {
164 panic(errorWithDetails(errAccessManagerLockedRole, ufmt.Sprintf("role %d is locked", role)))
165 }
166
167 state.ensureRole(role)
168
169 if role == AdminRole && state.isLastAdmin(account) {
170 panic(errAccessManagerLastAdmin)
171 }
172
173 return state.getRole(role).revoke(account)
174}
175
176// isLastAdmin reports whether account is the only currently active AdminRole
177// member. Prevents bricking the system by removing the final admin.
178func (state *State) isLastAdmin(account address) bool {
179 adminRole := state.getRole(AdminRole)
180 if adminRole == nil {
181 return false
182 }
183
184 now := CurrentTimePoint()
185 if !adminRole.hasMember(account, now) {
186 return false
187 }
188
189 for member, access := range adminRole.Members {
190 if member != account && access.isActiveAt(now) {
191 return false
192 }
193 }
194
195 return true
196}
197
198// RenounceRole revokes caller-confirmed role membership from account.
199// OpenZeppelin reference:
200// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L243-L249
201func (state *State) RenounceRole(role RoleId, account address, callerConfirmation address) bool {
202 if account != callerConfirmation {
203 panic(errAccessManagerBadConfirmation)
204 }
205
206 return state.RevokeRole(role, account)
207}
208
209// SetRoleAdmin sets the admin role that can grant or revoke role.
210// OpenZeppelin references:
211// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L251-L254
212// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L321-L337
213func (state *State) SetRoleAdmin(role RoleId, admin RoleId) {
214 RequireUnlockedConfigRole(role)
215
216 state.ensureRole(role)
217 state.getRole(role).setAdmin(admin)
218}
219
220// SetGrantDelay schedules newDelay behind a setback; it never overwrites the
221// active delay on the spot. Callers read GetRoleGrantDelay for the current
222// value or GetRoleGrantDelayEffect for the effect timePoint.
223// AdminRole is not locked here (unlike SetRoleAdmin) — OZ/Union only reject PublicRole.
224// OpenZeppelin: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L262-L264,L362-L371
225// Union: https://github.com/unionlabs/union/blob/8cff0ff34f6baa4cdb1e4650a08985dd05de0c5a/cosmwasm/access-manager/src/contract.rs#L139-L148,L314-L345
226func (state *State) SetGrantDelay(role RoleId, newDelay uint32) {
227 if role == PublicRole {
228 panic(errorWithDetails(errAccessManagerLockedRole, ufmt.Sprintf("role %d is locked", role)))
229 }
230
231 state.ensureRole(role)
232 state.getRole(role).setGrantDelay(CurrentTimePoint(), newDelay)
233}
234
235// ============================================= FUNCTION MANAGEMENT ==============================================
236
237// SetTargetFunctionRole sets the role required for target selector.
238// OpenZeppelin reference:
239// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L385-L393
240func (state *State) SetTargetFunctionRole(target string, selector Selector, role RoleId) {
241 state.ensureTarget(target)
242 state.getTarget(target).setFunctionRole(selector, role)
243}
244
245// SetTargetFunctionRoles sets the same role for multiple target selectors.
246// OpenZeppelin reference:
247// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L373-L383
248func (state *State) SetTargetFunctionRoles(target string, selectors []Selector, role RoleId) {
249 state.ensureTarget(target)
250 state.getTarget(target).setFunctionRoles(selectors, role)
251}
252
253// =============================================== MODE MANAGEMENT ================================================
254
255// SetTargetClosed sets whether target rejects calls regardless of selector role.
256// OpenZeppelin references:
257// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L412-L416
258// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L418-L426
259func (state *State) SetTargetClosed(target string, closed bool) {
260 state.ensureTarget(target)
261 state.getTarget(target).setClosed(closed)
262}
263
264// ================================================= ADMIN LOGIC ==================================================
265
266// CanAdminRole reports whether caller has the admin role required for role.
267// OpenZeppelin reference:
268// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L652-L657
269func (state *State) CanAdminRole(role RoleId, caller address) CanCallResult {
270 adminRole := state.GetRoleAdmin(role)
271
272 membership := state.HasRole(adminRole, caller)
273 if !membership.IsMember {
274 return NewCanCallResult(false)
275 }
276
277 return NewCanCallResult(true)
278}
279
280// CanManageTarget reports whether caller can manage target configuration.
281// OpenZeppelin references:
282// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L101-L105
283// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5fd1781b1454fd1ef8e722282f86f9293cacf256/contracts/access/manager/AccessManager.sol#L640-L650
284func (state *State) CanManageTarget(caller address) CanCallResult {
285 membership := state.HasRole(AdminRole, caller)
286 if !membership.IsMember {
287 return NewCanCallResult(false)
288 }
289
290 return NewCanCallResult(true)
291}
292
293func (state *State) getRole(role RoleId) *RoleConfig {
294 return state.Roles[role]
295}
296
297func (state *State) getTarget(target string) *TargetConfig {
298 return state.Targets[target]
299}
300
301func (state *State) ensureRole(role RoleId) {
302 roleConfig, found := state.Roles[role]
303 if !found || roleConfig == nil {
304 state.Roles[role] = NewRoleConfig(AdminRole, 0)
305 }
306}
307
308func (state *State) ensureTarget(target string) {
309 targetConfig, found := state.Targets[target]
310 if !found || targetConfig == nil {
311 state.Targets[target] = NewTargetConfig(false)
312 }
313}