|
1 |
| -use near_plugins::{Ownable, Upgradable}; |
| 1 | +use near_plugins::{access_control, AccessControlRole, AccessControllable, Upgradable}; |
2 | 2 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
|
| 3 | +use near_sdk::env; |
| 4 | +use near_sdk::serde::{Deserialize, Serialize}; |
3 | 5 | use near_sdk::{near_bindgen, AccountId, Duration, PanicOnDefault};
|
4 | 6 |
|
5 |
| -/// Deriving `Upgradable` requires the contract to be `Ownable.` |
| 7 | +/// Defines roles for access control of protected methods provided by the `Upgradable` plugin. |
| 8 | +#[derive(AccessControlRole, Deserialize, Serialize, Copy, Clone)] |
| 9 | +#[serde(crate = "near_sdk::serde")] |
| 10 | +pub enum Role { |
| 11 | + /// May successfully call any of the protected `Upgradable` methods since below it is passed to |
| 12 | + /// every attribute of `access_control_roles`. |
| 13 | + /// |
| 14 | + /// Using this pattern grantees of a single role are authorized to call all `Upgradable`methods. |
| 15 | + DAO, |
| 16 | + /// May successfully call `Upgradable::up_stage_code`, but none of the other protected methods, |
| 17 | + /// since below is passed only to the `code_stagers` attribute. |
| 18 | + /// |
| 19 | + /// Using this pattern grantees of a role are authorized to call only one particular protected |
| 20 | + /// `Upgradable` method. |
| 21 | + CodeStager, |
| 22 | + /// May successfully call `Upgradable::up_deploy_code`, but none of the other protected methods, |
| 23 | + /// since below is passed only to the `code_deployers` attribute. |
| 24 | + /// |
| 25 | + /// Using this pattern grantees of a role are authorized to call only one particular protected |
| 26 | + /// `Upgradable` method. |
| 27 | + CodeDeployer, |
| 28 | + /// May successfully call `Upgradable` methods to initialize and update the staging duration |
| 29 | + /// since below it is passed to the attributes `duration_initializers`, |
| 30 | + /// `duration_update_stagers`, and `duration_update_appliers`. |
| 31 | + /// |
| 32 | + /// Using this pattern grantees of a single role are authorized to call multiple (but not all) |
| 33 | + /// protected `Upgradable` methods. |
| 34 | + DurationManager, |
| 35 | +} |
| 36 | + |
| 37 | +/// Deriving `Upgradable` requires the contract to be `AccessControllable`. |
| 38 | +/// |
| 39 | +/// Variants of `Role` are passed to `upgradables`'s `access_control_roles` attribute to specify |
| 40 | +/// which roles are authorized to successfully call protected `Upgradable` methods. A protected |
| 41 | +/// method panics if it is called by an account which is not a grantee of at least one of the |
| 42 | +/// whitelisted roles. |
| 43 | +#[access_control(role_type(Role))] |
6 | 44 | #[near_bindgen]
|
7 |
| -#[derive(Ownable, Upgradable, PanicOnDefault, BorshDeserialize, BorshSerialize)] |
| 45 | +#[derive(Upgradable, PanicOnDefault, BorshDeserialize, BorshSerialize)] |
| 46 | +#[upgradable(access_control_roles( |
| 47 | + code_stagers(Role::CodeStager, Role::DAO), |
| 48 | + code_deployers(Role::CodeDeployer, Role::DAO), |
| 49 | + duration_initializers(Role::DurationManager, Role::DAO), |
| 50 | + duration_update_stagers(Role::DurationManager, Role::DAO), |
| 51 | + duration_update_appliers(Role::DurationManager, Role::DAO), |
| 52 | +))] |
8 | 53 | pub struct Contract;
|
9 | 54 |
|
10 | 55 | #[near_bindgen]
|
11 | 56 | impl Contract {
|
12 |
| - /// Parameter `owner` allows setting the owner in the constructor if an `AccountId` is provided. |
13 |
| - /// If `owner` is `None`, no owner will be set in the constructor. After contract initialization |
14 |
| - /// it is possible to set an owner with `Ownable::owner_set`. |
| 57 | + /// Makes the contract itself `AccessControllable` super admin to allow it granting and revoking |
| 58 | + /// permissions. If parameter `dao` is `Some(account_id)`, then `account_id` is granted |
| 59 | + /// `Role::DAO`. After initialization permissions can be managed using the methods provided by |
| 60 | + /// `AccessControllable`. |
15 | 61 | ///
|
16 | 62 | /// Parameter `staging_duration` allows initializing the time that is required to pass between
|
17 | 63 | /// staging and deploying code. This delay provides a safety mechanism to protect users against
|
18 | 64 | /// unfavorable or malicious code upgrades. If `staging_duration` is `None`, no staging duration
|
19 | 65 | /// will be set in the constructor. It is possible to set it later using
|
20 | 66 | /// `Upgradable::up_init_staging_duration`. If no staging duration is set, it defaults to zero,
|
21 | 67 | /// allowing immediate deployments of staged code.
|
22 |
| - /// |
23 |
| - /// Since this constructor uses an `*_unchecked` method, it should be combined with code |
24 |
| - /// deployment in a batch transaction. |
25 | 68 | #[init]
|
26 |
| - pub fn new(owner: Option<AccountId>, staging_duration: Option<Duration>) -> Self { |
| 69 | + pub fn new(dao: Option<AccountId>, staging_duration: Option<Duration>) -> Self { |
27 | 70 | let mut contract = Self;
|
28 | 71 |
|
29 |
| - // Optionally set the owner. |
30 |
| - if owner.is_some() { |
31 |
| - contract.owner_set(owner); |
| 72 | + // Make the contract itself access control super admin, allowing it to grant and revoke |
| 73 | + // permissions. |
| 74 | + near_sdk::require!( |
| 75 | + contract.acl_init_super_admin(env::current_account_id()), |
| 76 | + "Failed to initialize super admin", |
| 77 | + ); |
| 78 | + |
| 79 | + // Optionally grant `Role::DAO`. |
| 80 | + if let Some(account_id) = dao { |
| 81 | + let res = contract.acl_grant_role(Role::DAO.into(), account_id); |
| 82 | + assert_eq!(Some(true), res, "Failed to grant role"); |
32 | 83 | }
|
33 | 84 |
|
34 | 85 | // Optionally initialize the staging duration.
|
35 | 86 | if let Some(staging_duration) = staging_duration {
|
36 |
| - // The owner (set above) might be an account other than the contract itself. In that |
37 |
| - // case `Upgradable::up_init_staging_duration` would fail, since only the Owner may call |
38 |
| - // it successfully. Therefore we are using an (internal) unchecked method here. |
39 |
| - // |
40 |
| - // Avoid using `*_unchecked` functions in public contract methods that are not protected |
41 |
| - // by access control. Otherwise there is a risk of unwanted state changes carried out by |
42 |
| - // malicious users. For this example, we assume the constructor is called in a batch |
43 |
| - // transaction together with code deployment. |
44 |
| - contract.up_set_staging_duration_unchecked(staging_duration); |
| 87 | + // Temporarily grant `Role::DurationManager` to the contract to authorize it for |
| 88 | + // initializing the staging duration. Granting and revoking the role is possible since |
| 89 | + // the contract was made super admin above. |
| 90 | + contract.acl_grant_role(Role::DurationManager.into(), env::current_account_id()); |
| 91 | + contract.up_init_staging_duration(staging_duration); |
| 92 | + contract.acl_revoke_role(Role::DurationManager.into(), env::current_account_id()); |
45 | 93 | }
|
46 | 94 |
|
47 | 95 | contract
|
48 | 96 | }
|
| 97 | + |
| 98 | + /// Function to verify the contract was deployed and initialized successfully. |
| 99 | + pub fn is_set_up(&self) -> bool { |
| 100 | + true |
| 101 | + } |
49 | 102 | }
|
0 commit comments