use common_data::change_control::ChangeControl; use common_data::constants::GAS_FOR_CALLBACK; use near_plugins::{ access_control, access_control_any, pause, AccessControlRole, AccessControllable, Pausable, Upgradable, }; use near_sdk::{ borsh::{BorshDeserialize, BorshSerialize}, collections::Vector, env, json_types::Base64VecU8, near, AccountId, BorshStorageKey, CryptoHash, Gas, NearToken, PanicOnDefault, Promise, PromiseError, }; use serde::{Deserialize, Serialize}; use serde_json::json; const SMART_CONTRACT_INIT_GAS: Gas = Gas::from_tgas(150); #[derive(BorshSerialize, BorshStorageKey)] #[borsh(crate = "near_sdk::borsh")] enum StorageKey { Children, } #[derive(AccessControlRole, Deserialize, Serialize, Copy, Clone)] #[serde(crate = "near_sdk::serde")] pub enum Role { DAO, PauseManager, UnpauseManager, UpgradableCodeStager, UpgradableCodeDeployer, OpManager, } #[near(serializers = [borsh])] pub struct ContractData { latest_code_hash: CryptoHash, children: Vector, } #[near(serializers = [borsh])] pub enum VersionedContractData { Current(ContractData), } #[near(contract_state)] #[derive(Pausable, Upgradable, PanicOnDefault)] #[access_control(role_type(Role))] #[pausable(pause_roles(Role::PauseManager), unpause_roles(Role::UnpauseManager))] #[upgradable(access_control_roles( code_stagers(Role::UpgradableCodeStager, Role::DAO), code_deployers(Role::UpgradableCodeDeployer, Role::DAO), duration_initializers(Role::DAO), duration_update_stagers(Role::DAO), duration_update_appliers(Role::DAO), ))] pub struct Contract { data: VersionedContractData, } #[near] impl Contract { #[init] pub fn new(owner_id: Option, latest_code_hash: Base64VecU8) -> Self { let latest_code_hash: Vec = latest_code_hash.into(); let mut contract = Self { data: VersionedContractData::Current(ContractData { latest_code_hash: latest_code_hash.try_into().unwrap(), children: Vector::new(StorageKey::Children), }), }; let owner_id = owner_id.unwrap_or(env::predecessor_account_id()); contract.acl_init_super_admin(env::predecessor_account_id()); contract.acl_add_super_admin(owner_id.clone()); contract.acl_grant_role(Role::DAO.into(), owner_id.clone()); contract.acl_grant_role(Role::PauseManager.into(), owner_id.clone()); contract.acl_grant_role(Role::UnpauseManager.into(), owner_id.clone()); contract.acl_grant_role(Role::OpManager.into(), owner_id.clone()); contract } #[access_control_any(roles(Role::DAO))] pub fn set_latest_code_hash(&mut self, latest_code_hash: Base64VecU8) { let latest_code_hash: Vec = latest_code_hash.into(); self.data_mut().latest_code_hash = latest_code_hash.try_into().unwrap(); } pub fn children_count(&self) -> u64 { self.data().children.len() } pub fn get_children_paged( &self, from_index: Option, limit: Option, ) -> Vec { let from_index = from_index.unwrap_or(0); let limit = limit.unwrap_or(usize::MAX as u64) as usize; let size = self.data().children.len(); if from_index >= size { return vec![]; } let to_index = std::cmp::min(from_index + limit as u64, size) as usize; (from_index as usize..to_index) .map(|index| self.data().children.get(index as u64).unwrap()) .collect() } #[access_control_any(roles(Role::OpManager, Role::DAO))] #[payable] #[pause] pub fn create_kernel( &mut self, child_id: AccountId, policy_change_control: ChangeControl, owner: Option, ) -> Promise { let suffix = format!(".{}", env::current_account_id()); assert!( child_id.as_str().ends_with(&suffix), "Child ID must end with '{}'", suffix ); assert!( self.data().children.iter().all(|id| id != child_id), "Child ID '{}' is already in use", child_id ); Promise::new(child_id.clone()) .create_account() .transfer(env::attached_deposit()) .use_global_contract(self.data().latest_code_hash) .function_call( "new_with_config".to_string(), json!({ "policy_change_control": policy_change_control, "owner": owner, }) .to_string() .as_bytes() .to_vec(), NearToken::from_near(0), SMART_CONTRACT_INIT_GAS, ) .then( Promise::new(env::current_account_id()).function_call( "on_create_callback".to_string(), json!({ "child_id": child_id, }) .to_string() .as_bytes() .to_vec(), NearToken::from_near(0), GAS_FOR_CALLBACK, ), ) } #[private] pub fn on_create_callback( &mut self, #[callback_result] result: Result, PromiseError>, child_id: AccountId, ) { match result { Ok(_) => { self.data_mut().children.push(&child_id); env::log_str(&format!("Kernel contract '{}' created.", child_id)); } Err(_) => { env::log_str("Child contract creation failed."); } } } } impl Contract { pub fn data(&self) -> &ContractData { match &self.data { VersionedContractData::Current(data) => data, } } pub fn data_mut(&mut self) -> &mut ContractData { match &mut self.data { VersionedContractData::Current(data) => data, } } }