use common_data::constants::GAS_FOR_CALLBACK; use common_data::vault_config::VaultConfig; use common_data::{asset::Asset, protocol_config::ProtocolConfig}; 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(120); #[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, protocol_account: AccountId, protocol_config: ProtocolConfig, } #[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 owner_id = owner_id.unwrap_or(env::predecessor_account_id()); let mut contract = Self { data: VersionedContractData::Current(ContractData { latest_code_hash: latest_code_hash.try_into().unwrap(), children: Vector::new(StorageKey::Children), }), protocol_account: owner_id.clone(), protocol_config: ProtocolConfig::default(), }; 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_vault( &mut self, child_id: AccountId, owner_account_id: AccountId, base_asset: Asset, accepted_deposit_assets: Vec, share_token_symbol: String, share_token_name: String, share_token_decimals: u8, fee_recipient: AccountId, ) -> Promise { let vault_config = VaultConfig::default(); // Use same assets for both deposit and redeem by default let available_redeem_assets = accepted_deposit_assets.clone(); self.create_vault_with_config( child_id, owner_account_id, base_asset, accepted_deposit_assets, available_redeem_assets, share_token_symbol, share_token_name, share_token_decimals, vault_config, fee_recipient, ) } #[access_control_any(roles(Role::OpManager, Role::DAO))] #[payable] #[pause] pub fn create_vault_with_config( &mut self, child_id: AccountId, owner_account_id: AccountId, base_asset: Asset, accepted_deposit_assets: Vec, available_redeem_assets: Vec, share_token_symbol: String, share_token_name: String, share_token_decimals: u8, vault_config: VaultConfig, fee_recipient: AccountId, ) -> Promise { assert!( !accepted_deposit_assets.is_empty(), "Must provide at least one accepted deposit asset" ); assert!( !available_redeem_assets.is_empty(), "Must provide at least one available redeem asset" ); assert!( accepted_deposit_assets.contains(&base_asset), "Base asset must be included in accepted_deposit_assets" ); let suffix = format!(".{}", env::current_account_id()); assert!( child_id.as_str().ends_with(&suffix), "Child ID must end with '{}'", suffix ); vault_config.validate(); 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!({ "owner_account_id": owner_account_id, "base_asset": base_asset, "accepted_deposit_assets": accepted_deposit_assets, "available_redeem_assets": available_redeem_assets, "share_token_symbol": share_token_symbol, "share_token_name": share_token_name, "share_token_decimals": share_token_decimals, "share_token_icon": null, "vault_config": vault_config, "fee_recipient": fee_recipient, "protocol_account": self.protocol_account, "protocol_config": self.protocol_config, }) .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, ), ) } #[access_control_any(roles(Role::DAO))] pub fn set_protocol_account(&mut self, new_protocol_account: AccountId) { self.protocol_account = new_protocol_account; } #[access_control_any(roles(Role::DAO))] pub fn set_protocol_config(&mut self, new_protocol_config: ProtocolConfig) { self.protocol_config = new_protocol_config; } #[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!("Vault 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, } } }