Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions lib-blockchain/src/contracts/dao_registry/core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use crate::contracts::utils::id_generation;
use crate::contracts::integration::ContractEvent;
use crate::contracts::dao_registry::types::*;
use crate::integration::crypto_integration::PublicKey;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// DAO Registry contract
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DAORegistry {
/// Map from dao_id -> DAOEntry
pub registry: HashMap<[u8; 32], DAOEntry>,
/// Index from token_addr -> dao_id for fast lookup
pub token_index: HashMap<[u8; 32], [u8; 32]>,
}

impl DAORegistry {
/// Create empty registry
pub fn new() -> Self {
Self {
registry: HashMap::new(),
token_index: HashMap::new(),
}
}

/// Register a new DAO
/// Returns (dao_id, ContractEvent::DaoRegistered)
pub fn register_dao(
&mut self,
token_addr: [u8; 32],
class: String,
metadata_hash: Option<[u8; 32]>,
treasury: PublicKey,
owner: PublicKey,
) -> Result<([u8; 32], ContractEvent), String> {
if self.token_index.contains_key(&token_addr) {
return Err("DAO for token already registered".to_string());
}

// Generate unique DAO id using token_addr + owner + timestamp
let timestamp = crate::utils::time::current_timestamp();
let dao_id = id_generation::generate_contract_id(&[
&token_addr,
&owner.as_bytes(),
&timestamp.to_le_bytes(),
]);

let entry = DAOEntry {
dao_id,
token_addr,
class: class.clone(),
metadata_hash,
treasury: treasury.clone(),
owner: owner.clone(),
created_at: timestamp,
};

self.registry.insert(dao_id, entry);
self.token_index.insert(token_addr, dao_id);

let event = ContractEvent::DaoRegistered {
dao_id,
token_addr,
owner: owner.clone(),
treasury,
class,
metadata_hash,
};

Ok((dao_id, event))
}

/// Lookup a DAO by token address
pub fn get_dao(&self, token_addr: [u8; 32]) -> Result<DAOMetadata, String> {
match self.token_index.get(&token_addr) {
Some(dao_id) => match self.registry.get(dao_id) {
Some(entry) => Ok(DAOMetadata {
dao_id: entry.dao_id,
token_addr: entry.token_addr,
class: entry.class.clone(),
metadata_hash: entry.metadata_hash,
treasury: entry.treasury.clone(),
owner: entry.owner.clone(),
created_at: entry.created_at,
}),
None => Err("DAO entry not found for id".to_string()),
},
None => Err("DAO not found for token address".to_string()),
}
}

/// List all DAOs sorted by creation date ascending
/// Note: Pagination not implemented yet; consider adding (cursor, limit) later
pub fn list_daos(&self) -> Vec<DAOEntry> {
let mut all: Vec<DAOEntry> = self.registry.values().cloned().collect();
all.sort_by_key(|e| e.created_at);
all
}

/// Update metadata hash for a DAO. Only owner can update metadata.
/// Returns ContractEvent::DaoUpdated on success
pub fn update_metadata(
&mut self,
dao_id: [u8; 32],
updater: PublicKey,
metadata_hash: Option<[u8; 32]>,
) -> Result<ContractEvent, String> {
let entry = self
.registry
.get_mut(&dao_id)
.ok_or_else(|| "DAO not found".to_string())?;

if entry.owner != updater {
return Err("Only DAO owner can update metadata".to_string());
}

entry.metadata_hash = metadata_hash;

let event = ContractEvent::DaoUpdated {
dao_id,
updater,
metadata_hash,
};

Ok(event)
}
}
5 changes: 5 additions & 0 deletions lib-blockchain/src/contracts/dao_registry/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod core;
pub mod types;

pub use core::DAORegistry;
pub use types::{DAOEntry, DAOMetadata};
65 changes: 65 additions & 0 deletions lib-blockchain/src/contracts/dao_registry/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use super::*;
use crate::integration::crypto_integration::PublicKey;
use crate::contracts::dao_registry::DAORegistry;

fn test_public_key(id: u8) -> PublicKey {
PublicKey::new(vec![id; 32])
}

#[test]
fn test_register_and_get_dao() {
let mut reg = DAORegistry::new();
let token = [1u8; 32];
let owner = test_public_key(1);
let treasury = test_public_key(2);

let (dao_id, event) = reg
.register_dao(token, "NP".to_string(), None, treasury.clone(), owner.clone())
.expect("should register");

assert_eq!(event.event_type(), "DaoRegistered");

let meta = reg.get_dao(token).expect("should find dao");
assert_eq!(meta.dao_id, dao_id);
assert_eq!(meta.owner, owner);
}

#[test]
fn test_list_daos_sorted() {
let mut reg = DAORegistry::new();
let t1 = [1u8; 32];
let t2 = [2u8; 32];
let owner = test_public_key(1);
let treasury = test_public_key(2);

let _ = reg.register_dao(t2, "NP".to_string(), None, treasury.clone(), owner.clone());
std::thread::sleep(std::time::Duration::from_millis(5));
let _ = reg.register_dao(t1, "NP".to_string(), None, treasury.clone(), owner.clone());

let list = reg.list_daos();
assert!(list.len() >= 2);
assert!(list[0].created_at <= list[1].created_at);
}

#[test]
fn test_update_metadata_access_control() {
let mut reg = DAORegistry::new();
let token = [3u8; 32];
let owner = test_public_key(1);
let other = test_public_key(9);
let treasury = test_public_key(2);

let (dao_id, _) = reg
.register_dao(token, "NP".to_string(), None, treasury.clone(), owner.clone())
.expect("register");

// Unauthorized update
let res = reg.update_metadata(dao_id, other.clone(), Some([9u8; 32]));
assert!(res.is_err());

// Authorized update
let res = reg.update_metadata(dao_id, owner.clone(), Some([7u8; 32]));
assert!(res.is_ok());
let event = res.unwrap();
assert_eq!(event.event_type(), "DaoUpdated");
}
26 changes: 26 additions & 0 deletions lib-blockchain/src/contracts/dao_registry/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use serde::{Deserialize, Serialize};
use crate::integration::crypto_integration::PublicKey;

/// DAO entry stored in the registry
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DAOEntry {
pub dao_id: [u8; 32],
pub token_addr: [u8; 32],
pub class: String,
pub metadata_hash: Option<[u8; 32]>,
pub treasury: PublicKey,
pub owner: PublicKey,
pub created_at: u64,
}

/// Metadata view returned by queries
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DAOMetadata {
pub dao_id: [u8; 32],
pub token_addr: [u8; 32],
pub class: String,
pub metadata_hash: Option<[u8; 32]>,
pub treasury: PublicKey,
pub owner: PublicKey,
pub created_at: u64,
}
48 changes: 48 additions & 0 deletions lib-blockchain/src/contracts/integration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,21 @@ pub enum ContractEvent {
token_fees: u64,
treasury_addr: PublicKey,
},
/// New DAO registered
DaoRegistered {
dao_id: [u8; 32],
token_addr: [u8; 32],
owner: PublicKey,
treasury: PublicKey,
class: String,
metadata_hash: Option<[u8; 32]>,
},
/// DAO metadata updated
DaoUpdated {
dao_id: [u8; 32],
updater: PublicKey,
metadata_hash: Option<[u8; 32]>,
},
}

impl ContractEvent {
Expand All @@ -369,6 +384,8 @@ impl ContractEvent {
ContractEvent::SwapExecuted { .. } => "SwapExecuted",
ContractEvent::PoolCreated { .. } => "PoolCreated",
ContractEvent::FeeCollected { .. } => "FeeCollected",
ContractEvent::DaoRegistered { .. } => "DaoRegistered",
ContractEvent::DaoUpdated { .. } => "DaoUpdated",
}
}

Expand Down Expand Up @@ -511,6 +528,37 @@ mod tests {
}
}

#[test]
fn test_dao_event_serialization() {
let keypair1 = KeyPair::generate().unwrap();
let owner = keypair1.public_key.clone();
let treasury = PublicKey::new(vec![3u8; 32]);

let event = ContractEvent::DaoRegistered {
dao_id: [9u8; 32],
token_addr: [8u8; 32],
owner: owner.clone(),
treasury: treasury.clone(),
class: "NP".to_string(),
metadata_hash: Some([1u8; 32]),
};

let serialized = event.to_bytes().unwrap();
let deserialized = ContractEvent::from_bytes(&serialized).unwrap();

match deserialized {
ContractEvent::DaoRegistered { dao_id, token_addr, owner: o, treasury: t, class, metadata_hash } => {
assert_eq!(dao_id, [9u8; 32]);
assert_eq!(token_addr, [8u8; 32]);
assert_eq!(o, owner);
assert_eq!(t, treasury);
assert_eq!(class, "NP".to_string());
assert_eq!(metadata_hash, Some([1u8; 32]));
}
_ => panic!("Wrong event type"),
}
}

#[test]
fn test_blockchain_integration() {
let storage = MemoryStorage::default();
Expand Down
4 changes: 4 additions & 0 deletions lib-blockchain/src/contracts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub mod emergency_reserve;
#[cfg(feature = "contracts")]
pub mod sov_swap;
#[cfg(feature = "contracts")]
pub mod dao_registry;
#[cfg(feature = "contracts")]
pub mod utils;
#[cfg(feature = "contracts")]
pub mod web4;
Expand Down Expand Up @@ -69,6 +71,8 @@ pub use emergency_reserve::EmergencyReserve;
#[cfg(feature = "contracts")]
pub use sov_swap::{SovSwapPool, SwapDirection, SwapResult, PoolState, SwapError};
#[cfg(feature = "contracts")]
pub use dao_registry::{DAOEntry, DAORegistry, DAOMetadata};
#[cfg(feature = "contracts")]
pub use utils::*;
#[cfg(feature = "contracts")]
pub use web4::{Web4Contract, WebsiteContract, WebsiteMetadata, ContentRoute, DomainRecord, WebsiteDeploymentData};
Expand Down
Loading