@lazy-sol/access-control 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
v1.1.1: Role-based Access Control (RBAC) Inspector
|
2
|
+
- Introduced the Role-based Access Control (RBAC) Inspector UI capable of evaluating features and roles
|
3
|
+
for already deployed contracts; supports all EVM-based networks (mainnet, sepolia, etc.)
|
4
|
+
- Optimized npm imports moving all prod dependencies into dev dependencies
|
5
|
+
|
1
6
|
v1.1.0: Contact Size Optimizations
|
2
7
|
|
3
8
|
- __Breaking Change:__ Solidity 0.8.4 is now required to compile the contracts
|
package/README.md
CHANGED
@@ -65,7 +65,7 @@ features and roles. Usually, smart contracts use different values for all the fe
|
|
65
65
|
next section).
|
66
66
|
|
67
67
|
Access manager may revoke its own permissions, including the bit 255. Eventually that allows an access manager to let
|
68
|
-
the smart contract “float freely” and be controlled only by the community (via DAO) or by no one at all.
|
68
|
+
the smart contract “float freely” and be controlled only by the community (via the DAO) or by no one at all.
|
69
69
|
|
70
70
|
## Comparing with OpenZeppelin
|
71
71
|
|
@@ -73,7 +73,7 @@ Both our and OpenZeppelin Access Control implementations feature a similar API t
|
|
73
73
|
thing".
|
74
74
|
|
75
75
|
Zeppelin implementation is more flexible:
|
76
|
-
* it allows setting unlimited number of roles, while current is limited to 256 different roles
|
76
|
+
* it allows setting an unlimited number of roles, while current is limited to 256 different roles
|
77
77
|
* it allows setting an admin for each role, while current allows having only one global admin
|
78
78
|
|
79
79
|
Our implementation is more lightweight:
|
@@ -93,11 +93,11 @@ npm i -D @lazy-sol/access-control
|
|
93
93
|
Restricted function is a function with a `public` Solidity modifier access to which is restricted
|
94
94
|
so that only a pre-configured set of accounts can execute it.
|
95
95
|
|
96
|
-
1.
|
97
|
-
|
96
|
+
1. Enable role-based access control (RBAC) in a new smart contract
|
97
|
+
by inheriting the RBAC contract from the [AccessControlCore](./contracts/AccessControlCore.sol) contract:
|
98
98
|
```solidity
|
99
99
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
100
|
-
import "@lazy-sol/access-control/contracts/
|
100
|
+
import "@lazy-sol/access-control/contracts/AccessControlCore.sol";
|
101
101
|
|
102
102
|
/**
|
103
103
|
* @title Simple ERC20 Implementation
|
@@ -106,29 +106,29 @@ so that only a pre-configured set of accounts can execute it.
|
|
106
106
|
*
|
107
107
|
* @author Lazy So[u]l
|
108
108
|
*/
|
109
|
-
contract MyERC20Token is ERC20,
|
109
|
+
contract MyERC20Token is ERC20, AccessControlCore {
|
110
110
|
|
111
111
|
...
|
112
112
|
|
113
113
|
}
|
114
114
|
```
|
115
115
|
|
116
|
-
2.
|
116
|
+
2. Define an access control role with the unique integer value:
|
117
117
|
```solidity
|
118
118
|
...
|
119
119
|
|
120
120
|
/**
|
121
121
|
* @notice Token creator is responsible for creating (minting)
|
122
|
-
tokens to an arbitrary address
|
122
|
+
* tokens to an arbitrary address
|
123
123
|
* @dev Role ROLE_TOKEN_CREATOR allows minting tokens
|
124
|
-
(calling `mint` function)
|
124
|
+
* (calling `mint` function)
|
125
125
|
*/
|
126
126
|
uint32 public constant ROLE_TOKEN_CREATOR = 0x0001_0000;
|
127
127
|
|
128
128
|
...
|
129
129
|
```
|
130
130
|
|
131
|
-
3.
|
131
|
+
3. Add the `_requireSenderInRole(ROLE_TOKEN_CREATOR)` check into the function body:
|
132
132
|
```solidity
|
133
133
|
...
|
134
134
|
|
@@ -137,7 +137,7 @@ so that only a pre-configured set of accounts can execute it.
|
|
137
137
|
*/
|
138
138
|
function _mint(address _to, uint256 _value) internal virtual override {
|
139
139
|
// check if caller has sufficient permissions to mint tokens
|
140
|
-
|
140
|
+
_requireSenderInRole(ROLE_TOKEN_CREATOR);
|
141
141
|
|
142
142
|
// delegate to super implementation
|
143
143
|
super._mint(_to, _value);
|
@@ -146,8 +146,8 @@ so that only a pre-configured set of accounts can execute it.
|
|
146
146
|
...
|
147
147
|
```
|
148
148
|
|
149
|
-
|
150
|
-
|
149
|
+
Note: it is also possible to use the `restrictedTo` modifier in the function declaration instead of the `require`
|
150
|
+
in the function body if this better suits the coding style:
|
151
151
|
```solidity
|
152
152
|
...
|
153
153
|
|
@@ -162,6 +162,31 @@ so that only a pre-configured set of accounts can execute it.
|
|
162
162
|
...
|
163
163
|
```
|
164
164
|
|
165
|
+
### Customizing the Error Message
|
166
|
+
|
167
|
+
Modifier `restrictedTo()`, internal functions `_requireSenderInRole()` and `_requireAccessCondition()` throw the
|
168
|
+
`AccessDenied` denied error when access check fails.
|
169
|
+
|
170
|
+
It is also possible to use your own custom errors or string messages if needed by leveraging a lower level boolean
|
171
|
+
functions `_isSenderInRole()` and `_isOperatorInRole()`:
|
172
|
+
|
173
|
+
```solidity
|
174
|
+
...
|
175
|
+
|
176
|
+
/**
|
177
|
+
* @inheritdoc ERC20
|
178
|
+
*/
|
179
|
+
function _mint(address _to, uint256 _value) internal virtual override {
|
180
|
+
// check if caller has sufficient permissions to mint tokens
|
181
|
+
require(_isSenderInRole(ROLE_TOKEN_CREATOR), "access denied");
|
182
|
+
|
183
|
+
// delegate to super implementation
|
184
|
+
super._mint(_to, _value);
|
185
|
+
}
|
186
|
+
|
187
|
+
...
|
188
|
+
```
|
189
|
+
|
165
190
|
Examples:
|
166
191
|
[ERC20Impl](https://raw.githubusercontent.com/vgorin/solidity-template/master/contracts/token/ERC20Impl.sol),
|
167
192
|
[ERC721Impl](https://raw.githubusercontent.com/vgorin/solidity-template/master/contracts/token/ERC721Impl.sol).
|
@@ -177,32 +202,32 @@ Many deployed contracts are using this model, and when the time comes to switch
|
|
177
202
|
|
178
203
|
Prerequisite: deployed OZ Ownable contract (target contract) address (target_address)
|
179
204
|
|
180
|
-
1.
|
181
|
-
|
205
|
+
1. Deploy the AccessControl Adapter bound to the already deployed OZ Ownable contract
|
206
|
+
(specify the target OZ Ownable contract address in the constructor upon the deployment)
|
182
207
|
```javascript
|
183
208
|
const adapter = await (artifacts.require("OwnableToAccessControlAdapter")).new(target_address);
|
184
209
|
```
|
185
210
|
|
186
|
-
2.
|
187
|
-
|
211
|
+
2. Define what Ownable-restricted public functions on the target contract you'd like to be able
|
212
|
+
to provide access to through the adapter contract
|
188
213
|
|
189
|
-
3.
|
190
|
-
|
214
|
+
3. Map every such function with the role required to execute it using `updateAccessRole()` function
|
215
|
+
For example, to be able to provide an access to the transferOwnership(address) function, you could do
|
191
216
|
```javascript
|
192
217
|
const ROLE_TRANSFER_OWNERSHIP_MANAGER = 0x00010000;
|
193
218
|
await adapter.updateAccessRole("transferOwnership(address)", ROLE_TRANSFER_OWNERSHIP_MANAGER);
|
194
|
-
|
219
|
+
```
|
195
220
|
|
196
|
-
4.
|
197
|
-
|
198
|
-
|
221
|
+
4. Provide the roles to the corresponding operators as you would usually do with AccessControl
|
222
|
+
For example, if you wish an address 0x00000000000000000000000000000000000Ff1CE to grant an access to the
|
223
|
+
transferOwnership(address) function on the target, you could do
|
199
224
|
```javascript
|
200
225
|
const operator = "0x00000000000000000000000000000000000Ff1CE";
|
201
226
|
await adapter.updateRole(operator, ROLE_TRANSFER_OWNERSHIP_MANAGER);
|
202
227
|
```
|
203
228
|
|
204
|
-
5.
|
205
|
-
|
229
|
+
5. Transfer the ownership of the target contract to the deployed AccessControl Adapter contract
|
230
|
+
Note that you can also do steps 2-4 after the step 5
|
206
231
|
|
207
232
|
#### Usage Flow
|
208
233
|
|
@@ -210,16 +235,16 @@ Prerequisite: installed AccessControl Adapter with the access to at least one re
|
|
210
235
|
function configured
|
211
236
|
|
212
237
|
To execute the restricted access function on the target contract via the AccessControl Adapter
|
213
|
-
1.
|
214
|
-
|
215
|
-
|
238
|
+
1. Use target contract ABI to construct a low-level function call calldata
|
239
|
+
For example, to construct the transferOwnership() function calldata to transfer the ownership to the
|
240
|
+
0x00000000000000000000000000000000DEAdc0De address, you could do
|
216
241
|
```javascript
|
217
242
|
const to = "0x00000000000000000000000000000000DEAdc0De";
|
218
243
|
const calldata = target.contract.methods.transferOwnership(to).encodeABI();
|
219
244
|
```
|
220
245
|
|
221
|
-
2.
|
222
|
-
|
246
|
+
2. Execute a low-level function call on the AccessControl Adapter contract using the constructed calldata
|
247
|
+
For example, to execute the transferOwnership() function (prepared in step 1), you could do
|
223
248
|
```javascript
|
224
249
|
await web3.eth.sendTransaction({
|
225
250
|
from: operator,
|
@@ -228,9 +253,20 @@ To execute the restricted access function on the target contract via the AccessC
|
|
228
253
|
}
|
229
254
|
```
|
230
255
|
|
231
|
-
3.
|
232
|
-
|
233
|
-
|
256
|
+
3. It is also ok to add an ether to the transaction by adding a value field to the `sendTransaction` call,
|
257
|
+
as well as sending plain ether transfer transaction, as long as target contract has payable functions,
|
258
|
+
and/or has a default payable receiver
|
259
|
+
|
260
|
+
## Evaluating Currently Enabled Features and Roles on the Deployed Contract
|
261
|
+
|
262
|
+
1. To evaluate currently enabled features use
|
263
|
+
* `features()` function, or
|
264
|
+
* `getRole(this)` function, replacing `this` with the deployed contract address
|
265
|
+
2. To evaluate currently enabled permissions for a **particular** address use
|
266
|
+
* `getRole(address)` function
|
267
|
+
3. To find **all** the addresses having any permissions, track the `RoleUpdated()` event and evaluate the history
|
268
|
+
of `assiged` roles for every `operator` address
|
269
|
+
* Alternatively, use the [tool](ui.html) which automates the process
|
234
270
|
|
235
271
|
## Contributing
|
236
272
|
Please see the [Contribution Guide](./CONTRIBUTING.md) document to get understanding on how to report issues,
|
package/artifacts/contracts/OwnableToAccessControlAdapter.sol/OwnableToAccessControlAdapter.json
CHANGED
@@ -278,8 +278,8 @@
|
|
278
278
|
"type": "receive"
|
279
279
|
}
|
280
280
|
],
|
281
|
-
"bytecode": "
|
282
|
-
"deployedBytecode": "
|
281
|
+
"bytecode": "0x608060405234801561001057600080fd5b50604051610a23380380610a2383398101604081905261002f9161012f565b80600061003f82600019806100bb565b61004a3082806100bb565b50506001600160a01b0382166100955760405162461bcd60e51b815260206004820152600c60248201526b7a65726f206164647265737360a01b604482015260640160405180910390fd5b50600180546001600160a01b0319166001600160a01b0392909216919091179055610162565b6001600160a01b0383166000818152602081815260409182902084905581518581529081018490527fe9be537308880e0f56b7d7cfd7abf85f14c4934486d138f848b92a0cbaf659b4910160405180910390a2505050565b80516001600160a01b038116811461012a57600080fd5b919050565b6000806040838503121561014257600080fd5b61014b83610113565b915061015960208401610113565b90509250929050565b6108b2806101716000396000f3fe6080604052600436106100a05760003560e01c8063491d261111610064578063491d2611146101d35780635defb40d146101f3578063ae5b102e14610213578063ae682e2e14610233578063d4b839921461024b578063d5bb7f6714610283576100bf565b806309c5eabe146100ff5780630e82fe25146101285780632b5214161461016357806334e48c9e14610185578063442767331461019d576100bf565b366100bf576100bd604051806020016040528060008152506102a3565b005b6100bd6000368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506102a392505050565b61011261010d36600461064c565b6102a3565b60405161011f91906106ed565b60405180910390f35b34801561013457600080fd5b5061015561014336600461071d565b60026020526000908152604090205481565b60405190815260200161011f565b34801561016f57600080fd5b5030600090815260208190526040902054610155565b34801561019157600080fd5b50610155600160fd1b81565b3480156101a957600080fd5b506101556101b836600461074f565b6001600160a01b031660009081526020819052604090205490565b3480156101df57600080fd5b506100bd6101ee36600461076a565b610411565b3480156101ff57600080fd5b506100bd61020e366004610794565b610477565b34801561021f57600080fd5b506100bd61022e3660046107ed565b61048c565b34801561023f57600080fd5b50610155600160ff1b81565b34801561025757600080fd5b5060015461026b906001600160a01b031681565b6040516001600160a01b03909116815260200161011f565b34801561028f57600080fd5b506100bd61029e366004610809565b6104f5565b602081015181516060919015610323576001600160e01b03198116600090815260026020526040812054908190036103185760405162461bcd60e51b81526020600482015260136024820152721858d8d95cdcc81c9bdb19481b9bdd081cd95d606a1b60448201526064015b60405180910390fd5b61032181610502565b505b60015460405160009182916001600160a01b03909116903490610347908890610822565b60006040518083038185875af1925050503d8060008114610384576040519150601f19603f3d011682016040523d82523d6000602084013e610389565b606091505b5091509150816103ce5760405162461bcd60e51b815260206004820152601060248201526f195e1958dd5d1a5bdb8819985a5b195960821b604482015260640161030f565b7f57a62eca76fc623c92f161d2a4b851851ece707135ce2af1eec256d660571b6d8386836040516104019392919061083e565b60405180910390a1949350505050565b61041e600160fd1b610502565b6001600160e01b03198216600081815260026020908152604091829020849055815192835282018390527fdb8ed917742b49e83acd1322bcaa8f18b1e5f78a70784c43ea14db7ab50e628d910160405180910390a15050565b610488828051906020012082610411565b5050565b610499600160ff1b610502565b61048882826104f0336104c1876001600160a01b031660009081526020819052604090205490565b6001600160a01b0391909116600090815260208190526040902054600019808818821618908716919091171690565b610513565b6104ff308261048c565b50565b6104ff61050e8261056b565b61057d565b6001600160a01b0383166000818152602081815260409182902084905581518581529081018490527fe9be537308880e0f56b7d7cfd7abf85f14c4934486d138f848b92a0cbaf659b4910160405180910390a2505050565b6000610577338361059b565b92915050565b806104ff57604051634ca8886760e01b815260040160405180910390fd5b6001600160a01b038216600090815260208190526040812054821682145b9392505050565b634e487b7160e01b600052604160045260246000fd5b600067ffffffffffffffff808411156105f1576105f16105c0565b604051601f8501601f19908116603f01168101908282118183101715610619576106196105c0565b8160405280935085815286868601111561063257600080fd5b858560208301376000602087830101525050509392505050565b60006020828403121561065e57600080fd5b813567ffffffffffffffff81111561067557600080fd5b8201601f8101841361068657600080fd5b610695848235602084016105d6565b949350505050565b60005b838110156106b85781810151838201526020016106a0565b50506000910152565b600081518084526106d981602086016020860161069d565b601f01601f19169290920160200192915050565b6020815260006105b960208301846106c1565b80356001600160e01b03198116811461071857600080fd5b919050565b60006020828403121561072f57600080fd5b6105b982610700565b80356001600160a01b038116811461071857600080fd5b60006020828403121561076157600080fd5b6105b982610738565b6000806040838503121561077d57600080fd5b61078683610700565b946020939093013593505050565b600080604083850312156107a757600080fd5b823567ffffffffffffffff8111156107be57600080fd5b8301601f810185136107cf57600080fd5b6107de858235602084016105d6565b95602094909401359450505050565b6000806040838503121561080057600080fd5b61078683610738565b60006020828403121561081b57600080fd5b5035919050565b6000825161083481846020870161069d565b9190910192915050565b63ffffffff60e01b8416815260606020820152600061086060608301856106c1565b828103604084015261087281856106c1565b969550505050505056fea26469706673582212201ba377eb6a91d1e61a05796e2c3f7b98c258168e3db52e709dd2c3a003d3eb7864736f6c63430008150033",
|
282
|
+
"deployedBytecode": "0x6080604052600436106100a05760003560e01c8063491d261111610064578063491d2611146101d35780635defb40d146101f3578063ae5b102e14610213578063ae682e2e14610233578063d4b839921461024b578063d5bb7f6714610283576100bf565b806309c5eabe146100ff5780630e82fe25146101285780632b5214161461016357806334e48c9e14610185578063442767331461019d576100bf565b366100bf576100bd604051806020016040528060008152506102a3565b005b6100bd6000368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506102a392505050565b61011261010d36600461064c565b6102a3565b60405161011f91906106ed565b60405180910390f35b34801561013457600080fd5b5061015561014336600461071d565b60026020526000908152604090205481565b60405190815260200161011f565b34801561016f57600080fd5b5030600090815260208190526040902054610155565b34801561019157600080fd5b50610155600160fd1b81565b3480156101a957600080fd5b506101556101b836600461074f565b6001600160a01b031660009081526020819052604090205490565b3480156101df57600080fd5b506100bd6101ee36600461076a565b610411565b3480156101ff57600080fd5b506100bd61020e366004610794565b610477565b34801561021f57600080fd5b506100bd61022e3660046107ed565b61048c565b34801561023f57600080fd5b50610155600160ff1b81565b34801561025757600080fd5b5060015461026b906001600160a01b031681565b6040516001600160a01b03909116815260200161011f565b34801561028f57600080fd5b506100bd61029e366004610809565b6104f5565b602081015181516060919015610323576001600160e01b03198116600090815260026020526040812054908190036103185760405162461bcd60e51b81526020600482015260136024820152721858d8d95cdcc81c9bdb19481b9bdd081cd95d606a1b60448201526064015b60405180910390fd5b61032181610502565b505b60015460405160009182916001600160a01b03909116903490610347908890610822565b60006040518083038185875af1925050503d8060008114610384576040519150601f19603f3d011682016040523d82523d6000602084013e610389565b606091505b5091509150816103ce5760405162461bcd60e51b815260206004820152601060248201526f195e1958dd5d1a5bdb8819985a5b195960821b604482015260640161030f565b7f57a62eca76fc623c92f161d2a4b851851ece707135ce2af1eec256d660571b6d8386836040516104019392919061083e565b60405180910390a1949350505050565b61041e600160fd1b610502565b6001600160e01b03198216600081815260026020908152604091829020849055815192835282018390527fdb8ed917742b49e83acd1322bcaa8f18b1e5f78a70784c43ea14db7ab50e628d910160405180910390a15050565b610488828051906020012082610411565b5050565b610499600160ff1b610502565b61048882826104f0336104c1876001600160a01b031660009081526020819052604090205490565b6001600160a01b0391909116600090815260208190526040902054600019808818821618908716919091171690565b610513565b6104ff308261048c565b50565b6104ff61050e8261056b565b61057d565b6001600160a01b0383166000818152602081815260409182902084905581518581529081018490527fe9be537308880e0f56b7d7cfd7abf85f14c4934486d138f848b92a0cbaf659b4910160405180910390a2505050565b6000610577338361059b565b92915050565b806104ff57604051634ca8886760e01b815260040160405180910390fd5b6001600160a01b038216600090815260208190526040812054821682145b9392505050565b634e487b7160e01b600052604160045260246000fd5b600067ffffffffffffffff808411156105f1576105f16105c0565b604051601f8501601f19908116603f01168101908282118183101715610619576106196105c0565b8160405280935085815286868601111561063257600080fd5b858560208301376000602087830101525050509392505050565b60006020828403121561065e57600080fd5b813567ffffffffffffffff81111561067557600080fd5b8201601f8101841361068657600080fd5b610695848235602084016105d6565b949350505050565b60005b838110156106b85781810151838201526020016106a0565b50506000910152565b600081518084526106d981602086016020860161069d565b601f01601f19169290920160200192915050565b6020815260006105b960208301846106c1565b80356001600160e01b03198116811461071857600080fd5b919050565b60006020828403121561072f57600080fd5b6105b982610700565b80356001600160a01b038116811461071857600080fd5b60006020828403121561076157600080fd5b6105b982610738565b6000806040838503121561077d57600080fd5b61078683610700565b946020939093013593505050565b600080604083850312156107a757600080fd5b823567ffffffffffffffff8111156107be57600080fd5b8301601f810185136107cf57600080fd5b6107de858235602084016105d6565b95602094909401359450505050565b6000806040838503121561080057600080fd5b61078683610738565b60006020828403121561081b57600080fd5b5035919050565b6000825161083481846020870161069d565b9190910192915050565b63ffffffff60e01b8416815260606020820152600061086060608301856106c1565b828103604084015261087281856106c1565b969550505050505056fea26469706673582212201ba377eb6a91d1e61a05796e2c3f7b98c258168e3db52e709dd2c3a003d3eb7864736f6c63430008150033",
|
283
283
|
"linkReferences": {},
|
284
284
|
"deployedLinkReferences": {}
|
285
285
|
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
// hardhat (0.4.11)
|
6
6
|
pragma solidity >=0.8.4;
|
7
7
|
|
8
|
-
import
|
8
|
+
import "./AccessControlCore.sol";
|
9
9
|
|
10
10
|
/**
|
11
11
|
* @title Role-based Access Control (RBAC)
|
@@ -56,7 +56,7 @@ import {AccessControlCore} from "./AccessControlCore.sol";
|
|
56
56
|
*
|
57
57
|
* @author Basil Gorin
|
58
58
|
*/
|
59
|
-
contract AccessControl is AccessControlCore {
|
59
|
+
abstract contract AccessControl is AccessControlCore {
|
60
60
|
/**
|
61
61
|
* @notice Creates an access control instance, setting the contract owner to have full privileges
|
62
62
|
*
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lazy-sol/access-control",
|
3
|
-
"version": "1.1.
|
3
|
+
"version": "1.1.1",
|
4
4
|
"description": "Enable the modular plug and play (PnP) architecture for your dapp by incorporating the role-based access control (RBAC) into the smart contracts",
|
5
5
|
"main": "index.js",
|
6
6
|
"scripts": {
|
@@ -21,14 +21,12 @@
|
|
21
21
|
],
|
22
22
|
"author": "Basil Gorin",
|
23
23
|
"license": "MIT",
|
24
|
-
"dependencies": {
|
25
|
-
"@lazy-sol/a-missing-gem": "^1.0.9",
|
26
|
-
"@nomiclabs/hardhat-truffle5": "^2.0.7",
|
27
|
-
"hardhat": "^2.22.9",
|
28
|
-
"hardhat-deploy": "^0.11.45"
|
29
|
-
},
|
30
24
|
"devDependencies": {
|
25
|
+
"@lazy-sol/a-missing-gem": "^1.0.10",
|
31
26
|
"@lazy-sol/zeppelin-test-helpers": "^1.0.5",
|
27
|
+
"@nomiclabs/hardhat-truffle5": "^2.0.7",
|
28
|
+
"hardhat": "^2.22.13",
|
29
|
+
"hardhat-deploy": "^0.11.45",
|
32
30
|
"hardhat-gas-reporter": "^1.0.10",
|
33
31
|
"solidity-coverage": "^0.8.13"
|
34
32
|
},
|
@@ -3,23 +3,14 @@
|
|
3
3
|
* (doesn't return any value on successful operation)
|
4
4
|
*
|
5
5
|
* @param a0 smart contract owner
|
6
|
-
* @param H0 initial token holder address
|
7
6
|
* @returns USDT ERC20 instance
|
8
7
|
*/
|
9
|
-
async function deploy_usdt(a0
|
8
|
+
async function deploy_usdt(a0) {
|
10
9
|
// smart contracts required
|
11
10
|
const USDTContract = artifacts.require("TetherToken");
|
12
11
|
|
13
|
-
// deploy the token
|
14
|
-
|
15
|
-
|
16
|
-
// move the initial supply if required
|
17
|
-
if(H0 !== a0) {
|
18
|
-
await token.transfer(H0, S0, {from: a0});
|
19
|
-
}
|
20
|
-
|
21
|
-
// return the reference
|
22
|
-
return token;
|
12
|
+
// deploy the token and return the reference
|
13
|
+
return await USDTContract.new(0, "Tether USD", "USDT", 6, {from: a0});
|
23
14
|
}
|
24
15
|
|
25
16
|
/**
|
package/ui.html
ADDED
@@ -0,0 +1,506 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>Role-based Access Control (RBAC) Inspector</title>
|
6
|
+
<!-- Include Web3.js from a CDN -->
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
|
8
|
+
<style>
|
9
|
+
table#roles_list {
|
10
|
+
width: 100%;
|
11
|
+
border-collapse: collapse;
|
12
|
+
}
|
13
|
+
table#roles_list th, table#roles_list td {
|
14
|
+
border: 1px solid grey;
|
15
|
+
}
|
16
|
+
table#roles_list tr td {
|
17
|
+
font-family: monospace;
|
18
|
+
}
|
19
|
+
</style>
|
20
|
+
</head>
|
21
|
+
<body>
|
22
|
+
<fieldset id="root_container" style="display: none;"><legend id="connection_state" style="text-align: right;"></legend>
|
23
|
+
<div id="main_app" style="display: none;">
|
24
|
+
<fieldset><legend>Role-based Access Control (RBAC) Features and Roles Inspector</legend>
|
25
|
+
<form onsubmit="inspect_features_roles(event)">
|
26
|
+
<table>
|
27
|
+
<tr>
|
28
|
+
<td><label for="contract_address">Contract Address:</label></td>
|
29
|
+
<td><input id="contract_address" type="text" required size="42" style="font-family: monospace;" value="0xA9f50518f9119d5F7A324EC76481c5246970dEa6"/></td>
|
30
|
+
</tr>
|
31
|
+
<tr><td></td><td><input type="submit" value="Inspect"/></td></tr>
|
32
|
+
</table>
|
33
|
+
</form>
|
34
|
+
</fieldset>
|
35
|
+
<div id="features_roles_container" style="display: none;">
|
36
|
+
<fieldset>
|
37
|
+
<legend id="features_title">RBAC Features</legend>
|
38
|
+
<div id="features_hex">Loading...</div>
|
39
|
+
</fieldset>
|
40
|
+
<fieldset>
|
41
|
+
<legend id="roles_list_title">RBAC Operators and Roles</legend>
|
42
|
+
<table id="roles_list"><tr><td>Loading...</td></tr></table>
|
43
|
+
</fieldset>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
</fieldset>
|
47
|
+
<div style="position: fixed; left: 0; bottom: 0; margin: 0.1em 0.2em;">© 2024 <a href="https://github.com/lazy-sol/">Lazy So[u]l</a></div>
|
48
|
+
<div style="position: fixed; right: 0; bottom: 0; margin: 0.1em 0.2em; font-family: monospace;">
|
49
|
+
[<a href="https://lazy-sol.github.io/advanced-erc20/ui.html">Advanced ERC20</a>]
|
50
|
+
[<a href="https://lazy-sol.github.io/tiny-erc721/ui.html">Tiny ERC721</a>]
|
51
|
+
[RBAC Inspector]
|
52
|
+
</div>
|
53
|
+
</body>
|
54
|
+
<script type="text/javascript">
|
55
|
+
// verify if MetaMask is installed
|
56
|
+
if(!window.ethereum) {
|
57
|
+
if(window.confirm("MetaMask is not installed. You will be redirected to the MetaMask installation page.")) {
|
58
|
+
window.setTimeout(function() {
|
59
|
+
window.location.href = "https://metamask.io/download/";
|
60
|
+
}, 0);
|
61
|
+
}
|
62
|
+
window.stop();
|
63
|
+
}
|
64
|
+
</script>
|
65
|
+
|
66
|
+
<script type="text/javascript">
|
67
|
+
// static HTML page anchors
|
68
|
+
const root_container = document.getElementById("root_container");
|
69
|
+
const connection_state = document.getElementById("connection_state");
|
70
|
+
const main_app = document.getElementById("main_app");
|
71
|
+
const contract_address = document.getElementById("contract_address");
|
72
|
+
const features_roles_container = document.getElementById("features_roles_container");
|
73
|
+
const features_title = document.getElementById("features_title");
|
74
|
+
const features_hex = document.getElementById("features_hex");
|
75
|
+
const roles_list_title = document.getElementById("roles_list_title");
|
76
|
+
const roles_list = document.getElementById("roles_list");
|
77
|
+
</script>
|
78
|
+
|
79
|
+
<script type="text/javascript">
|
80
|
+
// auxiliary pure functions
|
81
|
+
|
82
|
+
// auxiliary function to log and display non-fatal error, doesn't stop execution
|
83
|
+
function non_fatal_error(message, error) {
|
84
|
+
console.error("%s: %o", message, error);
|
85
|
+
window.alert(message + (error? ": " + json_stringify(error): ""));
|
86
|
+
}
|
87
|
+
|
88
|
+
// auxiliary function to log and display fatal error and stop execution
|
89
|
+
function fatal_error(message, error) {
|
90
|
+
non_fatal_error("FATAL: " + message, error);
|
91
|
+
window.stop();
|
92
|
+
throw error? error: "error";
|
93
|
+
}
|
94
|
+
|
95
|
+
// save JSON.stringify works properly with BigInt
|
96
|
+
function json_stringify(input) {
|
97
|
+
return JSON.stringify(input, (key, value) =>
|
98
|
+
typeof value === 'bigint' ? value.toString() : value
|
99
|
+
);
|
100
|
+
}
|
101
|
+
</script>
|
102
|
+
|
103
|
+
<script type="text/javascript">
|
104
|
+
// config and app state
|
105
|
+
|
106
|
+
// config stores settings which don't change
|
107
|
+
const CONF = {
|
108
|
+
// supported networks, where our contracts are known to be deployed
|
109
|
+
chains: {
|
110
|
+
1: {
|
111
|
+
name: "Mainnet",
|
112
|
+
block_explorer: {
|
113
|
+
tx: "https://etherscan.io/tx/",
|
114
|
+
address: "https://etherscan.io/address/",
|
115
|
+
token: "https://etherscan.io/token/",
|
116
|
+
},
|
117
|
+
},
|
118
|
+
11155111: {
|
119
|
+
name: "Sepolia",
|
120
|
+
block_explorer: {
|
121
|
+
tx: "https://sepolia.etherscan.io/tx/",
|
122
|
+
address: "https://sepolia.etherscan.io/address/",
|
123
|
+
token: "https://sepolia.etherscan.io/token/",
|
124
|
+
},
|
125
|
+
},
|
126
|
+
8453: {
|
127
|
+
name: "Base",
|
128
|
+
block_explorer: {
|
129
|
+
tx: "https://basescan.org/tx/",
|
130
|
+
address: "https://basescan.org/address/",
|
131
|
+
token: "https://basescan.org/token/",
|
132
|
+
},
|
133
|
+
},
|
134
|
+
84532: {
|
135
|
+
name: "Base Sepolia",
|
136
|
+
block_explorer: {
|
137
|
+
tx: "https://sepolia.basescan.org//tx/",
|
138
|
+
address: "https://sepolia.basescan.org/address/",
|
139
|
+
token: "https://sepolia.basescan.org/token/",
|
140
|
+
},
|
141
|
+
},
|
142
|
+
},
|
143
|
+
};
|
144
|
+
|
145
|
+
// state stores settings which may change due to user actions,
|
146
|
+
// due to the async nature of the app, many incoming events, it makes sense to follow
|
147
|
+
// the state of the app by listening to all the events and updating the state
|
148
|
+
const STATE = {
|
149
|
+
// currently connected network (recognized decimal Chain ID)
|
150
|
+
chain_id: undefined,
|
151
|
+
// currently connected account (accounts[0])
|
152
|
+
A0: undefined,
|
153
|
+
// currently connected chain config
|
154
|
+
chain: function() {
|
155
|
+
return this.chain_id? CONF.chains[this.chain_id]: undefined;
|
156
|
+
},
|
157
|
+
// function to display the chain we're connected to
|
158
|
+
update_chain_id: function(chain_id) {
|
159
|
+
this.chain_id = chain_id? parseInt(chain_id): undefined;
|
160
|
+
},
|
161
|
+
// function to update currently connected account
|
162
|
+
update_A0: function(accounts) {
|
163
|
+
// MetaMask is locked or not connected
|
164
|
+
if(!accounts || !accounts.length) {
|
165
|
+
this.A0 = undefined;
|
166
|
+
}
|
167
|
+
else {
|
168
|
+
[this.A0] = accounts;
|
169
|
+
}
|
170
|
+
},
|
171
|
+
// function to refresh the connection state UI
|
172
|
+
refresh_ui: function() {
|
173
|
+
if(CONF.chains[this.chain_id]) {
|
174
|
+
if(this.A0) {
|
175
|
+
connection_state.innerHTML = `Connected <span style="font-family: monospace;">${this.A0}</span>`;
|
176
|
+
}
|
177
|
+
else {
|
178
|
+
connection_state.innerHTML = `
|
179
|
+
MetaMask is not connected.
|
180
|
+
<input type="button" onclick="metamask_connect()" value="Connect"/>
|
181
|
+
`;
|
182
|
+
}
|
183
|
+
}
|
184
|
+
else if(this.chain_id) {
|
185
|
+
connection_state.innerHTML = `
|
186
|
+
Unsupported Chain ID: ${this.chain_id}
|
187
|
+
<input type="button" onclick="add_sepolia_network()" value="Switch to Sepolia"/>
|
188
|
+
`;
|
189
|
+
}
|
190
|
+
else {
|
191
|
+
connection_state.innerHTML = `
|
192
|
+
MetaMask disconnected.
|
193
|
+
<input type="button" onclick="metamask_connect()" value="Connect"/>
|
194
|
+
`;
|
195
|
+
}
|
196
|
+
root_container.style["display"] = "block";
|
197
|
+
},
|
198
|
+
// function to check if we're connected to supported network
|
199
|
+
connected: function() {
|
200
|
+
return this.chain() && this.A0;
|
201
|
+
},
|
202
|
+
}
|
203
|
+
</script>
|
204
|
+
|
205
|
+
<script type="text/javascript">
|
206
|
+
// main app section
|
207
|
+
|
208
|
+
// define window.ethereum shortcut
|
209
|
+
const ethereum = window.ethereum;
|
210
|
+
// MetaMask is installed, create a new Web3 instance
|
211
|
+
const web3 = new Web3(window.ethereum);
|
212
|
+
|
213
|
+
// function to connect to MetaMask
|
214
|
+
function metamask_connect() {
|
215
|
+
ethereum.request({ method: 'eth_requestAccounts' }).then(function(accounts) {
|
216
|
+
STATE.update_A0(accounts);
|
217
|
+
STATE.refresh_ui();
|
218
|
+
reload_main_app();
|
219
|
+
}).catch(function(e) {
|
220
|
+
non_fatal_error("Error connecting MetaMask", e);
|
221
|
+
});
|
222
|
+
}
|
223
|
+
|
224
|
+
// check if MetaMask is connected
|
225
|
+
ethereum.request({ method: 'eth_accounts' }).then(function(accounts) {
|
226
|
+
// check current connected network
|
227
|
+
ethereum.request({ method: 'eth_chainId' }).then(function(chain_id) {
|
228
|
+
STATE.update_chain_id(chain_id);
|
229
|
+
STATE.update_A0(accounts);
|
230
|
+
STATE.refresh_ui();
|
231
|
+
reload_main_app();
|
232
|
+
}).catch(function(e) {
|
233
|
+
fatal_error("Error getting Chain ID", e);
|
234
|
+
});
|
235
|
+
}).catch(function(e) {
|
236
|
+
fatal_error("Error connecting to MetaMask", e);
|
237
|
+
});
|
238
|
+
|
239
|
+
// Handle the case when MetaMask connects another account
|
240
|
+
ethereum.on('accountsChanged', function(accounts) {
|
241
|
+
console.log("account has been switched", accounts)
|
242
|
+
STATE.update_A0(accounts);
|
243
|
+
STATE.refresh_ui();
|
244
|
+
reload_main_app();
|
245
|
+
});
|
246
|
+
|
247
|
+
// Handle the case when MetaMask is connected
|
248
|
+
ethereum.on('connect', function(connect_info) {
|
249
|
+
console.log('MetaMask has been connected', connect_info);
|
250
|
+
STATE.update_chain_id(connect_info.chainId);
|
251
|
+
STATE.refresh_ui();
|
252
|
+
reload_main_app();
|
253
|
+
});
|
254
|
+
|
255
|
+
// MetaMask disconnect listener
|
256
|
+
ethereum.on('disconnect', function(error) {
|
257
|
+
console.warn('MetaMask has been disconnected', error);
|
258
|
+
STATE.update_A0();
|
259
|
+
STATE.update_chain_id();
|
260
|
+
STATE.refresh_ui();
|
261
|
+
reload_main_app();
|
262
|
+
});
|
263
|
+
|
264
|
+
// network switch listener
|
265
|
+
ethereum.on('chainChanged', function(chain_id) {
|
266
|
+
console.log('network has been changed', chain_id)
|
267
|
+
STATE.update_chain_id(chain_id);
|
268
|
+
STATE.refresh_ui();
|
269
|
+
reload_main_app();
|
270
|
+
});
|
271
|
+
|
272
|
+
// define an async routine to switch the network to Sepolia
|
273
|
+
async function add_sepolia_network() {
|
274
|
+
const sepolia_chain_id = '0xaa36a7'; // Hexadecimal chain ID for Sepolia
|
275
|
+
|
276
|
+
try {
|
277
|
+
// Check if Sepolia is already added
|
278
|
+
await ethereum.request({
|
279
|
+
method: 'wallet_switchEthereumChain',
|
280
|
+
params: [{ chainId: sepolia_chain_id }],
|
281
|
+
});
|
282
|
+
}
|
283
|
+
catch(e) {
|
284
|
+
// Error code 4902 indicates that the chain has not been added to MetaMask
|
285
|
+
if(e.code !== 4902) {
|
286
|
+
// Handle errors when switching the network
|
287
|
+
fatal_error("Error switching network to Sepolia", e)
|
288
|
+
}
|
289
|
+
|
290
|
+
try {
|
291
|
+
// Add the Sepolia network
|
292
|
+
await ethereum.request({
|
293
|
+
method: 'wallet_addEthereumChain',
|
294
|
+
params: [{
|
295
|
+
chainId: sepolia_chain_id,
|
296
|
+
rpcUrl: 'https://sepolia.infura.io/v3/',
|
297
|
+
chainName: 'Sepolia Test Network',
|
298
|
+
nativeCurrency: {
|
299
|
+
name: 'SepoliaETH',
|
300
|
+
symbol: 'SepoliaETH',
|
301
|
+
decimals: 18,
|
302
|
+
},
|
303
|
+
blockExplorerUrls: ['https://sepolia.etherscan.io/'],
|
304
|
+
}],
|
305
|
+
});
|
306
|
+
}
|
307
|
+
catch (e) {
|
308
|
+
// Handle errors when adding the network
|
309
|
+
fatal_error("Error adding Sepolia network", e);
|
310
|
+
}
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
// writes the entire roles_list table
|
315
|
+
function features_roles_update({features, roles, assignments}) {
|
316
|
+
if(!Array.isArray(roles)) {
|
317
|
+
roles = Object.entries(roles).map(([operator, role]) => Object.assign({}, {operator, role}));
|
318
|
+
}
|
319
|
+
|
320
|
+
if(assignments && assignments.length) {
|
321
|
+
features_hex.innerHTML = features?
|
322
|
+
`Features enabled: <span style="font-family: monospace">0x${features.toString(16).toUpperCase()}</span>`:
|
323
|
+
"No features enabled.";
|
324
|
+
|
325
|
+
if(roles && roles.length) {
|
326
|
+
const table_body = roles.map(render_role_row).join("\n");
|
327
|
+
roles_list.innerHTML = `<tr><th>Operator Address</th><th>Assigned Role</th></tr>\n${table_body}`;
|
328
|
+
}
|
329
|
+
else {
|
330
|
+
roles_list.innerHTML = "No roles assigned.";
|
331
|
+
}
|
332
|
+
}
|
333
|
+
else {
|
334
|
+
features_hex.innerHTML = "No features enabled or not an RBAC contract.";
|
335
|
+
roles_list.innerHTML = "No roles assigned or not an RBAC contract.";
|
336
|
+
}
|
337
|
+
features_roles_container.style["display"] = "block";
|
338
|
+
}
|
339
|
+
|
340
|
+
// renders an HTML of the role list table row
|
341
|
+
function render_role_row({operator, role}) {
|
342
|
+
return `<tr><td>${operator}</td><td>0x${role.toString(16).toUpperCase()}</td></tr>`;
|
343
|
+
}
|
344
|
+
|
345
|
+
// reloads the entire app, used when network or accounts have been changed
|
346
|
+
function reload_main_app() {
|
347
|
+
if(!STATE.connected()) {
|
348
|
+
main_app.style["display"] = "none";
|
349
|
+
return;
|
350
|
+
}
|
351
|
+
|
352
|
+
features_roles_container.style.display = "none";
|
353
|
+
features_title.innerHTML = "RBAC Features";
|
354
|
+
features_hex.innerHTML = "Loading features...";
|
355
|
+
roles_list_title.innerHTML = "RBAC Operators and Roles";
|
356
|
+
roles_list.innerHTML = "Loading operators and roles...";
|
357
|
+
|
358
|
+
main_app.style["display"] = "block";
|
359
|
+
}
|
360
|
+
|
361
|
+
// function to extract RBAC roles from the target contract
|
362
|
+
async function extract_rbac_features_roles(target_address) {
|
363
|
+
// the earliest versions of the AccessControl emitted both FeaturesUpdated and RoleUpdated;
|
364
|
+
// the features were assigned to a zero address
|
365
|
+
// TODO: the earliest versions didn't emit an event on deployment
|
366
|
+
// later versions of the AccessControl emitted only RoleUpdated event;
|
367
|
+
// the features were assigned to a zero address, and later to the contract address (self-role)
|
368
|
+
// the most recent versions of the AccessControl emit only RoleUpdated event;
|
369
|
+
// the features are assigned to the contract address, argument naming is improved and made clearer
|
370
|
+
const v0_abi = [{
|
371
|
+
// the earliest versions only
|
372
|
+
"name": "FeaturesUpdated",
|
373
|
+
"type": "event",
|
374
|
+
"anonymous": false,
|
375
|
+
"inputs": [
|
376
|
+
{"indexed": true, "internalType": "address", "name": "_by", "type": "address"},
|
377
|
+
{"indexed": false, "internalType": "uint256", "name": "_requested", "type": "uint256"},
|
378
|
+
{"indexed": false, "internalType": "uint256", "name": "_actual", "type": "uint256"}
|
379
|
+
],
|
380
|
+
}, {
|
381
|
+
// both the earliest and later versions
|
382
|
+
"name": "RoleUpdated",
|
383
|
+
"type": "event",
|
384
|
+
"anonymous": false,
|
385
|
+
"inputs": [
|
386
|
+
{"indexed": true, "internalType": "address", "name": "_by", "type": "address"},
|
387
|
+
{"indexed": true, "internalType": "address", "name": "_to", "type": "address"},
|
388
|
+
{"indexed": false, "internalType": "uint256", "name": "_requested", "type": "uint256"},
|
389
|
+
{"indexed": false, "internalType": "uint256", "name": "_actual", "type": "uint256"}
|
390
|
+
],
|
391
|
+
}, {
|
392
|
+
// both the earliest and later versions
|
393
|
+
"name": "userRoles",
|
394
|
+
"type": "function",
|
395
|
+
"inputs": [{"internalType": "address", "name": "", "type": "address"}],
|
396
|
+
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
|
397
|
+
"stateMutability": "view",
|
398
|
+
}];
|
399
|
+
const v1_abi = [{
|
400
|
+
// the latest versions only
|
401
|
+
"name": "RoleUpdated",
|
402
|
+
"type": "event",
|
403
|
+
"anonymous": false,
|
404
|
+
"inputs": [
|
405
|
+
{"indexed": true, "internalType": "address", "name": "operator", "type": "address"},
|
406
|
+
{"indexed": false, "internalType": "uint256", "name": "requested", "type": "uint256"},
|
407
|
+
{"indexed": false, "internalType": "uint256", "name": "assigned", "type": "uint256"}
|
408
|
+
],
|
409
|
+
}, {
|
410
|
+
// the latest versions only
|
411
|
+
"name": "getRole",
|
412
|
+
"type": "function",
|
413
|
+
"inputs": [{"internalType": "address", "name": "operator", "type": "address"}],
|
414
|
+
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
|
415
|
+
"stateMutability": "view",
|
416
|
+
}];
|
417
|
+
|
418
|
+
const v0_contract = new web3.eth.Contract(v0_abi, target_address);
|
419
|
+
const v1_contract = new web3.eth.Contract(v1_abi, target_address);
|
420
|
+
|
421
|
+
const features_v0 = await v0_contract.getPastEvents("FeaturesUpdated", {
|
422
|
+
fromBlock: "earliest",
|
423
|
+
});
|
424
|
+
console.log("v0.getPastEvents(FeaturesUpdated)", features_v0);
|
425
|
+
const role_v0 = await v0_contract.getPastEvents("RoleUpdated", {
|
426
|
+
fromBlock: "earliest",
|
427
|
+
});
|
428
|
+
console.log("v0.getPastEvents(RoleUpdated)", role_v0);
|
429
|
+
const role_v1 = await v1_contract.getPastEvents("RoleUpdated", {
|
430
|
+
fromBlock: "earliest",
|
431
|
+
});
|
432
|
+
console.log("v1.getPastEvents(RoleUpdated)", role_v1);
|
433
|
+
const assignments = features_v0.map(function(event) {
|
434
|
+
const {_actual: assigned} = event.returnValues;
|
435
|
+
// fix to the most recent format: feature is self-role
|
436
|
+
return {operator: target_address, assigned};
|
437
|
+
}).concat(role_v0.map(function(event) {
|
438
|
+
let {_to: operator, _actual: assigned} = event.returnValues;
|
439
|
+
if(operator === "0x0000000000000000000000000000000000000000") {
|
440
|
+
// fix to the most recent format: feature is self-role
|
441
|
+
operator = target_address;
|
442
|
+
}
|
443
|
+
return {operator, assigned};
|
444
|
+
})).concat(role_v1.map(function(event) {
|
445
|
+
const {operator, assigned} = event.returnValues;
|
446
|
+
return {operator, assigned};
|
447
|
+
}));
|
448
|
+
|
449
|
+
let features = 0;
|
450
|
+
v0_contract.methods["userRoles"]();
|
451
|
+
|
452
|
+
const roles = {};
|
453
|
+
assignments.forEach(function({operator, assigned}) {
|
454
|
+
if(operator === target_address) {
|
455
|
+
features = assigned;
|
456
|
+
}
|
457
|
+
else if(!BigInt(assigned)) {
|
458
|
+
delete roles[operator];
|
459
|
+
}
|
460
|
+
else {
|
461
|
+
roles[operator] = assigned;
|
462
|
+
}
|
463
|
+
});
|
464
|
+
console.log("extracted RBAC features and roles", features, roles);
|
465
|
+
|
466
|
+
return {features, roles, assignments};
|
467
|
+
}
|
468
|
+
|
469
|
+
// loads and parses features and roles on the RBAC contract,
|
470
|
+
// always returns false since is used as a form submit listener
|
471
|
+
function inspect_features_roles(e) {
|
472
|
+
if(e && e.preventDefault) {
|
473
|
+
e.preventDefault();
|
474
|
+
}
|
475
|
+
|
476
|
+
if(!contract_address.value) {
|
477
|
+
alert("Contract Address is required");
|
478
|
+
return false;
|
479
|
+
}
|
480
|
+
if(!STATE.connected()) {
|
481
|
+
alert("Not connected to the network. Please reload the page if the problem persists.")
|
482
|
+
return false;
|
483
|
+
}
|
484
|
+
|
485
|
+
web3.eth.getCode(contract_address.value).then(function(code) {
|
486
|
+
if(code.length < 4) {
|
487
|
+
alert("no code at " + contract_address.value);
|
488
|
+
}
|
489
|
+
}).catch(function(e) {
|
490
|
+
non_fatal_error("can't load contract at " + contract_address.value, e);
|
491
|
+
});
|
492
|
+
|
493
|
+
extract_rbac_features_roles(contract_address.value).then(features_roles_update).catch(function(e) {
|
494
|
+
console.warn("failed to load RBAC features and roles list", e);
|
495
|
+
features_hex.innerHTML = "Failed to load RBAC features: " + e;
|
496
|
+
roles_list.innerHTML = "Failed to load RBAC roles list: " + e;
|
497
|
+
features_roles_container.style["display"] = "block";
|
498
|
+
}).finally(function() {
|
499
|
+
features_title.innerHTML = "RBAC Features for " + contract_address.value;
|
500
|
+
roles_list_title.innerHTML = "RBAC Operators and Roles for " + contract_address.value;
|
501
|
+
});
|
502
|
+
|
503
|
+
return false;
|
504
|
+
}
|
505
|
+
</script>
|
506
|
+
</html>
|