#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
pub use self::upgrade_tracker::{Error, UpgradeTracker, UpgradeTrackerRef};
use ink_lang as ink;
use polymesh_api::{ink::extension::PolymeshEnvironment, Api};
pub type Hash = <PolymeshEnvironment as ink_env::Environment>::Hash;
pub type SpecVersion = u32;
pub type TxVersion = u32;
pub type WrappedApi = ([u8; 4], u32);
#[derive(
Default,
Clone,
Copy,
Eq,
PartialEq,
Ord,
PartialOrd,
scale::Encode,
scale::Decode
)]
#[cfg_attr(feature = "std", derive(Debug, scale_info::TypeInfo))]
#[derive(ink_storage::traits::SpreadLayout)]
#[derive(ink_storage::traits::PackedLayout)]
#[cfg_attr(feature = "std", derive(ink_storage::traits::StorageLayout))]
pub struct ChainVersion {
pub spec: SpecVersion,
pub tx: TxVersion,
}
impl ChainVersion {
pub fn current() -> Option<Self> {
let api = Api::new();
Some(Self {
spec: api.runtime().get_spec_version().ok()?,
tx: api.runtime().get_transaction_version().ok()?,
})
}
}
#[derive(
Default,
Clone,
Copy,
Eq,
PartialEq,
Ord,
PartialOrd,
scale::Encode,
scale::Decode
)]
#[cfg_attr(feature = "std", derive(Debug, scale_info::TypeInfo))]
#[derive(ink_storage::traits::SpreadLayout)]
#[derive(ink_storage::traits::PackedLayout)]
#[cfg_attr(feature = "std", derive(ink_storage::traits::StorageLayout))]
pub struct Upgrade {
pub chain_version: ChainVersion,
pub hash: Hash,
}
#[ink::contract(env = PolymeshEnvironment)]
pub mod upgrade_tracker {
use crate::*;
use alloc::vec::Vec;
use ink_storage::{traits::SpreadAllocate, Mapping};
use ink_prelude::collections::BTreeSet;
#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct UpgradeTracker {
admin: AccountId,
apis: Vec<WrappedApi>,
upgrades: Mapping<WrappedApi, BTreeSet<Upgrade>>,
}
#[ink(event)]
pub struct AdminSet {
#[ink(topic)]
old: Option<AccountId>,
#[ink(topic)]
new: AccountId,
}
#[ink(event)]
pub struct UpgradeWrappedApi {
#[ink(topic)]
admin: AccountId,
#[ink(topic)]
api: WrappedApi,
#[ink(topic)]
upgrade: Upgrade,
}
#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
NotAdmin,
NoChainVersion,
NoUpgrade,
}
pub type Result<T> = core::result::Result<T, Error>;
impl UpgradeTracker {
#[ink(constructor)]
pub fn new() -> Self {
ink_lang::utils::initialize_contract(|contract| Self::new_init(contract))
}
fn new_init(&mut self) {
self.admin = Self::env().caller();
self.env().emit_event(AdminSet {
old: None,
new: self.admin,
});
}
fn ensure_admin(&self) -> Result<()> {
let caller = self.env().caller();
if caller != self.admin {
ink_env::debug_println!(
"caller {:?} does not have sufficient permissions, only {:?} does",
caller,
self.admin
);
Err(Error::NotAdmin)
} else {
Ok(())
}
}
#[ink(message)]
pub fn set_admin(&mut self, new_admin: AccountId) -> Result<()> {
self.ensure_admin()?;
self.env().emit_event(AdminSet {
old: Some(self.admin),
new: new_admin,
});
self.admin = new_admin;
Ok(())
}
#[ink(message)]
pub fn get_admin(&self) -> AccountId {
self.admin
}
#[ink(message)]
pub fn upgrade_wrapped_api(&mut self, api: WrappedApi, upgrade: Upgrade) -> Result<()> {
self.ensure_admin()?;
let mut upgrades = self.upgrades.get(&api).unwrap_or_default();
upgrades.retain(|x| x.chain_version != upgrade.chain_version);
upgrades.insert(upgrade);
if upgrades.len() > 10 {
let old = upgrades.iter().next().copied();
if let Some(old) = old {
upgrades.remove(&old);
}
}
self.env().emit_event(UpgradeWrappedApi {
admin: self.admin,
api,
upgrade,
});
self.upgrades.insert(api, &upgrades);
if !self.apis.contains(&api) {
self.apis.push(api);
}
Ok(())
}
#[ink(message)]
pub fn get_latest_upgrade(&self, api: WrappedApi) -> Result<Hash> {
let upgrades = self.upgrades.get(&api).ok_or(Error::NoUpgrade)?;
let current = ChainVersion::current().ok_or(Error::NoChainVersion)?;
upgrades.into_iter()
.rfind(|u| u.chain_version <= current)
.map(|u| u.hash)
.ok_or(Error::NoUpgrade)
}
#[ink(message)]
pub fn get_apis(&self) -> Vec<WrappedApi> {
self.apis.clone()
}
#[ink(message)]
pub fn get_api_upgrades(&self, api: WrappedApi) -> Result<BTreeSet<Upgrade>> {
Ok(self.upgrades.get(&api).ok_or(Error::NoUpgrade)?)
}
}
}