use anyhow::anyhow;
use cid::Cid;
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::tuple::*;
use fvm_shared::{address::Address, ActorID};
use integer_encoding::VarInt;
use recall_fil_actors_runtime::{runtime::Runtime, ActorError, Map2, MapKey, DEFAULT_HAMT_CONFIG};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Display;
use std::str::FromStr;
type MachineCodeMap<BS> = Map2<BS, Kind, Cid>;
type DeployerMap<BS> = Map2<BS, Address, ()>;
type OwnerMap<BS> = Map2<BS, Address, Vec<Metadata>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PermissionModeParams {
Unrestricted,
AllowList(Vec<Address>),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PermissionMode {
Unrestricted,
AllowList(Cid), }
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Kind {
Bucket,
Timehub,
}
impl MapKey for Kind {
fn from_bytes(b: &[u8]) -> Result<Self, String> {
if let Some((result, size)) = u64::decode_var(b) {
if size != b.len() {
return Err(format!("trailing bytes after varint in {:?}", b));
}
match result {
0 => Ok(Kind::Bucket),
1 => Ok(Kind::Timehub),
_ => Err(format!("failed to decode kind from {}", result)),
}
} else {
Err(format!("failed to decode varint in {:?}", b))
}
}
fn to_bytes(&self) -> Result<Vec<u8>, String> {
let int = match self {
Self::Bucket => 0,
Self::Timehub => 1,
};
Ok(int.encode_var_vec())
}
}
impl FromStr for Kind {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"bucket" => Self::Bucket,
"timehub" => Self::Timehub,
_ => return Err(anyhow!("invalid machine kind")),
})
}
}
impl Display for Kind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Self::Bucket => "bucket",
Self::Timehub => "timehub",
};
write!(f, "{}", str)
}
}
#[derive(Debug, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct Metadata {
pub kind: Kind,
pub address: Address,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize_tuple, Deserialize_tuple)]
pub struct State {
pub machine_codes: Cid,
pub permission_mode: PermissionMode,
pub owners: Cid,
}
impl State {
pub fn new<BS: Blockstore>(
store: &BS,
machine_codes: HashMap<Kind, Cid>,
permission_mode: PermissionModeParams,
) -> Result<State, ActorError> {
let mut machine_code_map = MachineCodeMap::empty(store, DEFAULT_HAMT_CONFIG, "machines");
for (kind, code) in machine_codes {
machine_code_map.set(&kind, code)?;
}
let machine_codes = machine_code_map.flush()?;
let permission_mode = match permission_mode {
PermissionModeParams::Unrestricted => PermissionMode::Unrestricted,
PermissionModeParams::AllowList(deployers) => {
let mut deployers_map = DeployerMap::empty(store, DEFAULT_HAMT_CONFIG, "deployers");
for d in deployers {
deployers_map.set(&d, ())?;
}
PermissionMode::AllowList(deployers_map.flush()?)
}
};
let owners = OwnerMap::empty(store, DEFAULT_HAMT_CONFIG, "owners").flush()?;
Ok(State { machine_codes, permission_mode, owners })
}
pub fn get_machine_code<BS: Blockstore>(
&self,
store: &BS,
kind: &Kind,
) -> Result<Option<Cid>, ActorError> {
let machine_code_map =
MachineCodeMap::load(store, &self.machine_codes, DEFAULT_HAMT_CONFIG, "machines")?;
let code = machine_code_map.get(kind).map(|c| c.cloned())?;
Ok(code)
}
pub fn set_deployers<BS: Blockstore>(
&mut self,
store: &BS,
deployers: Vec<Address>,
) -> anyhow::Result<()> {
match self.permission_mode {
PermissionMode::Unrestricted => {
return Err(anyhow::anyhow!(
"cannot set deployers in unrestricted permission mode"
));
}
PermissionMode::AllowList(_) => {
let mut deployers_map = DeployerMap::empty(store, DEFAULT_HAMT_CONFIG, "deployers");
for d in deployers {
deployers_map.set(&d, ())?;
}
self.permission_mode = PermissionMode::AllowList(deployers_map.flush()?);
}
}
Ok(())
}
pub fn can_deploy(&self, rt: &impl Runtime, deployer: ActorID) -> Result<bool, ActorError> {
Ok(match &self.permission_mode {
PermissionMode::Unrestricted => true,
PermissionMode::AllowList(cid) => {
let deployer_map =
DeployerMap::load(rt.store(), cid, DEFAULT_HAMT_CONFIG, "deployers")?;
let mut allowed = false;
deployer_map.for_each(|k, _| {
allowed = allowed || rt.resolve_address(&k) == Some(deployer);
Ok(())
})?;
allowed
}
})
}
pub fn set_metadata<BS: Blockstore>(
&mut self,
store: &BS,
owner: Address,
address: Address,
kind: Kind,
metadata: HashMap<String, String>,
) -> anyhow::Result<()> {
let mut owner_map = OwnerMap::load(store, &self.owners, DEFAULT_HAMT_CONFIG, "owners")?;
let mut machine_metadata =
owner_map.get(&owner)?.map(|machines| machines.to_owned()).unwrap_or_default();
machine_metadata.push(Metadata { kind, address, metadata });
owner_map.set(&owner, machine_metadata)?;
self.owners = owner_map.flush()?;
Ok(())
}
pub fn get_metadata<BS: Blockstore>(
&self,
store: &BS,
owner: Address,
) -> anyhow::Result<Vec<Metadata>> {
let owner_map = OwnerMap::load(store, &self.owners, DEFAULT_HAMT_CONFIG, "owners")?;
let metadata = owner_map.get(&owner)?.map(|m| m.to_owned()).unwrap_or_default();
Ok(metadata)
}
}
#[cfg(test)]
mod tests {
use cid::Cid;
use crate::state::PermissionMode;
#[test]
fn test_serialization() {
let p = PermissionMode::Unrestricted;
let v = fvm_ipld_encoding::to_vec(&p).unwrap();
let dp: PermissionMode = fvm_ipld_encoding::from_slice(&v).unwrap();
assert_eq!(dp, p);
let p = PermissionMode::AllowList(Cid::default());
let v = fvm_ipld_encoding::to_vec(&p).unwrap();
let dp: PermissionMode = fvm_ipld_encoding::from_slice(&v).unwrap();
assert_eq!(dp, p)
}
}