use miden_client::Serializable;
use miden_protocol::Word;
use miden_protocol::account::{Account, AccountId, StorageSlotContent, StorageSlotName};
use crate::error::{MultisigError, Result};
const OZ_MULTISIG_THRESHOLD_CONFIG: &str = "openzeppelin::multisig::threshold_config";
const OZ_MULTISIG_SIGNER_PUBKEYS: &str = "openzeppelin::multisig::signer_public_keys";
const OZ_PSM_SELECTOR: &str = "openzeppelin::psm::selector";
const OZ_PSM_PUBLIC_KEY: &str = "openzeppelin::psm::public_key";
const STD_THRESHOLD_CONFIG: &str =
"miden::standards::auth::falcon512_rpo_multisig::threshold_config";
const STD_APPROVER_PUBKEYS: &str =
"miden::standards::auth::falcon512_rpo_multisig::approver_public_keys";
#[derive(Debug, Clone)]
pub struct MultisigAccount {
account: Account,
psm_endpoint: String,
}
impl MultisigAccount {
pub fn new(account: Account, psm_endpoint: impl Into<String>) -> Self {
Self {
account,
psm_endpoint: psm_endpoint.into(),
}
}
pub fn id(&self) -> AccountId {
self.account.id()
}
pub fn nonce(&self) -> u64 {
self.account.nonce().as_int()
}
pub fn commitment(&self) -> Word {
self.account.commitment()
}
pub fn psm_endpoint(&self) -> &str {
&self.psm_endpoint
}
pub fn inner(&self) -> &Account {
&self.account
}
pub fn into_inner(self) -> Account {
self.account
}
fn get_item_by_names(&self, names: &[&str]) -> Option<Word> {
for name in names {
if let Ok(slot_name) = StorageSlotName::new(*name)
&& let Ok(value) = self.account.storage().get_item(&slot_name)
{
return Some(value);
}
}
None
}
fn get_map_item_by_names(&self, names: &[&str], key: Word) -> Option<Word> {
for name in names {
if let Ok(slot_name) = StorageSlotName::new(*name)
&& let Ok(value) = self.account.storage().get_map_item(&slot_name, key)
{
return Some(value);
}
}
None
}
fn find_map_slot_name(&self, candidates: &[&str]) -> Option<String> {
for slot in self.account.storage().slots() {
let name_str = slot.name().as_str();
if candidates.contains(&name_str)
&& matches!(slot.content(), StorageSlotContent::Map(_))
{
return Some(name_str.to_string());
}
}
None
}
pub fn threshold(&self) -> Result<u32> {
let slot_value = self
.get_item_by_names(&[OZ_MULTISIG_THRESHOLD_CONFIG, STD_THRESHOLD_CONFIG])
.ok_or_else(|| {
MultisigError::AccountStorage("threshold config slot not found".to_string())
})?;
Ok(slot_value[0].as_int() as u32)
}
pub fn num_signers(&self) -> Result<u32> {
let slot_value = self
.get_item_by_names(&[OZ_MULTISIG_THRESHOLD_CONFIG, STD_THRESHOLD_CONFIG])
.ok_or_else(|| {
MultisigError::AccountStorage("threshold config slot not found".to_string())
})?;
Ok(slot_value[1].as_int() as u32)
}
pub fn cosigner_commitments(&self) -> Vec<Word> {
let mut commitments = Vec::new();
let Some(slot_name) =
self.find_map_slot_name(&[OZ_MULTISIG_SIGNER_PUBKEYS, STD_APPROVER_PUBKEYS])
else {
return commitments;
};
let key_zero = Word::from([0u32, 0, 0, 0]);
let slot_name_ref = StorageSlotName::new(slot_name.clone()).ok();
let Some(slot_name_ref) = slot_name_ref else {
return commitments;
};
let first_entry = self
.account
.storage()
.get_map_item(&slot_name_ref, key_zero);
if first_entry.is_err() || first_entry.as_ref().unwrap() == &Word::default() {
return commitments;
}
let mut index = 0u32;
loop {
let key = Word::from([index, 0, 0, 0]);
match self.account.storage().get_map_item(&slot_name_ref, key) {
Ok(value) if value != Word::default() => {
commitments.push(value);
index += 1;
}
_ => break,
}
}
commitments
}
pub fn cosigner_commitments_hex(&self) -> Vec<String> {
self.cosigner_commitments()
.into_iter()
.map(|word| format!("0x{}", hex::encode(word.to_bytes())))
.collect()
}
pub fn is_cosigner(&self, commitment: &Word) -> bool {
self.cosigner_commitments().contains(commitment)
}
pub fn psm_enabled(&self) -> Result<bool> {
let slot_value = self.get_item_by_names(&[OZ_PSM_SELECTOR]).ok_or_else(|| {
MultisigError::AccountStorage("PSM selector slot not found".to_string())
})?;
Ok(slot_value[0].as_int() == 1)
}
pub fn psm_commitment(&self) -> Result<Word> {
let key = Word::from([0u32, 0, 0, 0]);
self.get_map_item_by_names(&[OZ_PSM_PUBLIC_KEY], key)
.ok_or_else(|| {
MultisigError::AccountStorage("PSM public key slot not found".to_string())
})
}
}