Skip to content

Commit

Permalink
Merge pull request #88 from ctindogaru/policy-too-large
Browse files Browse the repository at this point in the history
Partially update the policy of a DAO by introducing smaller proposal kinds
  • Loading branch information
ctindogaru authored Jan 14, 2022
2 parents 4e9a1d5 + 67893cf commit 2e56755
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 4 deletions.
Binary file modified sputnik-staking/res/sputnik_staking.wasm
Binary file not shown.
Binary file modified sputnikdao-factory/res/sputnikdao_factory.wasm
Binary file not shown.
Binary file modified sputnikdao-factory2/res/sputnikdao_factory2.wasm
Binary file not shown.
Binary file modified sputnikdao/res/sputnikdao.wasm
Binary file not shown.
10 changes: 9 additions & 1 deletion sputnikdao2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ near view $SPUTNIK_ID get_policy
```rs
ProposalKind::ChangeConfig { .. },
ProposalKind::ChangePolicy { .. },
ProposalKind::ChangePolicyAddOrUpdateRole { .. },
ProposalKind::ChangePolicyRemoveRole { .. },
ProposalKind::ChangePolicyUpdateDefaultVotePolicy { .. },
ProposalKind::ChangePolicyUpdateParameters { .. },
ProposalKind::AddMemberToRole { .. },
ProposalKind::RemoveMemberFromRole { .. },
ProposalKind::FunctionCall { .. },
Expand All @@ -273,7 +277,11 @@ ProposalKind::FactoryInfoUpdate { .. },
```

- **ChangeConfig** - used to change the configuration of the DAO
- **ChangePolicy** - used to change the policy of the DAO
- **ChangePolicy** - used to change the full policy of the DAO
- **ChangePolicyAddOrUpdateRole** - used to add a new role to the policy of the DAO. If the role already exists, update it.
- **ChangePolicyRemoveRole** - used to remove a role from the policy of the DAO.
- **ChangePolicyUpdateDefaultVotePolicy** - used to update the default vote policy from the policy of the DAO.
- **ChangePolicyUpdateParameters** - used to update the parameters from the policy of the DAO. Parameters include: proposal bond, proposal period, bounty bond, bounty forgiveness period.
- **AddMemberToRole** - used to add a member to a role in the DAO
- **RemoveMemberFromRole** - used to remove a member from a role in the DAO
- **FunctionCall** - used to a call a function on any valid account on the network including the DAO itself, any other DAO, or any other contract. This is a useful mechanism for extending the capabilities of the DAO without modifying or complicating the DAO contract code. One can imagine a family of contracts built specifically to serve the DAO as agents, proxies, oracles and banks, for example.
Expand Down
Binary file modified sputnikdao2/res/sputnikdao2.wasm
Binary file not shown.
219 changes: 216 additions & 3 deletions sputnikdao2/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use near_sdk::json_types::{U128, U64};
use near_sdk::serde::{Deserialize, Serialize};
use near_sdk::{env, AccountId, Balance};

use crate::proposals::{Proposal, ProposalKind, ProposalStatus, Vote};
use crate::proposals::{PolicyParameters, Proposal, ProposalKind, ProposalStatus, Vote};
use crate::types::Action;

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)]
Expand Down Expand Up @@ -237,8 +237,52 @@ impl VersionedPolicy {
}

impl Policy {
///
/// Doesn't fail, because will be used on the finalization of the proposal.
pub fn add_or_update_role(&mut self, role: &RolePermission) {
for i in 0..self.roles.len() {
if &self.roles[i].name == &role.name {
env::log_str(&format!(
"Updating existing role in the policy:{}",
&role.name
));
let _ = std::mem::replace(&mut self.roles[i], role.clone());
return;
}
}
env::log_str(&format!("Adding new role to the policy:{}", &role.name));
self.roles.push(role.clone());
}

pub fn remove_role(&mut self, role: &String) {
for i in 0..self.roles.len() {
if &self.roles[i].name == role {
self.roles.remove(i);
return;
}
}
env::log_str(&format!("ERR_ROLE_NOT_FOUND:{}", role));
}

pub fn update_default_vote_policy(&mut self, vote_policy: &VotePolicy) {
self.default_vote_policy = vote_policy.clone();
env::log_str("Successfully updated the default vote policy.");
}

pub fn update_parameters(&mut self, parameters: &PolicyParameters) {
if parameters.proposal_bond.is_some() {
self.proposal_bond = parameters.proposal_bond.unwrap();
}
if parameters.proposal_period.is_some() {
self.proposal_period = parameters.proposal_period.unwrap();
}
if parameters.bounty_bond.is_some() {
self.bounty_bond = parameters.bounty_bond.unwrap();
}
if parameters.bounty_forgiveness_period.is_some() {
self.bounty_forgiveness_period = parameters.bounty_forgiveness_period.unwrap();
}
env::log_str("Successfully updated the policy parameters.");
}

pub fn add_member_to_role(&mut self, role: &String, member_id: &AccountId) {
for i in 0..self.roles.len() {
if &self.roles[i].name == role {
Expand Down Expand Up @@ -394,6 +438,8 @@ impl Policy {

#[cfg(test)]
mod tests {
use near_sdk::test_utils::accounts;

use super::*;

#[test]
Expand All @@ -407,4 +453,171 @@ mod tests {
let r2 = WeightOrRatio::Ratio(1, 1);
assert_eq!(r2.to_weight(5), 5);
}

#[test]
fn test_add_role() {
let council = vec![accounts(0), accounts(1)];
let mut policy = default_policy(council);

let community_role = policy.internal_get_role(&String::from("community"));
assert!(community_role.is_none());

let name: String = "community".to_string();
let kind: RoleKind = RoleKind::Group(vec![accounts(2), accounts(3)].into_iter().collect());
let permissions: HashSet<String> = vec!["*:*".to_string()].into_iter().collect();
let vote_policy: HashMap<String, VotePolicy> = HashMap::default();
let new_role = RolePermission {
name: name.clone(),
kind: kind.clone(),
permissions: permissions.clone(),
vote_policy: vote_policy.clone(),
};
assert_eq!(2, policy.roles.len());
policy.add_or_update_role(&new_role);
assert_eq!(3, policy.roles.len());

let community_role = policy.internal_get_role(&String::from("community"));
assert!(community_role.is_some());

let community_role = community_role.unwrap();
assert_eq!(name, community_role.name);
assert_eq!(kind, community_role.kind);
assert_eq!(permissions, community_role.permissions);
assert_eq!(vote_policy, community_role.vote_policy);
}

#[test]
fn test_update_role() {
let council = vec![accounts(0), accounts(1)];
let mut policy = default_policy(council);

let name: String = "council".to_string();
let kind: RoleKind = RoleKind::Group(vec![accounts(0), accounts(1)].into_iter().collect());
let permissions: HashSet<String> = vec![
"*:AddProposal".to_string(),
"*:VoteApprove".to_string(),
"*:VoteReject".to_string(),
"*:VoteRemove".to_string(),
"*:Finalize".to_string(),
]
.into_iter()
.collect();
let vote_policy: HashMap<String, VotePolicy> = HashMap::default();

let council_role = policy.internal_get_role(&String::from("council"));
assert!(council_role.is_some());

let council_role = council_role.unwrap();
assert_eq!(name, council_role.name);
assert_eq!(kind, council_role.kind);
assert_eq!(permissions, council_role.permissions);
assert_eq!(vote_policy, council_role.vote_policy);

let kind: RoleKind = RoleKind::Group(vec![accounts(2), accounts(3)].into_iter().collect());
let permissions: HashSet<String> = vec!["*:*".to_string()].into_iter().collect();
let updated_role = RolePermission {
name: name.clone(),
kind: kind.clone(),
permissions: permissions.clone(),
vote_policy: vote_policy.clone(),
};
assert_eq!(2, policy.roles.len());
policy.add_or_update_role(&updated_role);
assert_eq!(2, policy.roles.len());

let council_role = policy.internal_get_role(&String::from("council"));
assert!(council_role.is_some());

let council_role = council_role.unwrap();
assert_eq!(name, council_role.name);
assert_eq!(kind, council_role.kind);
assert_eq!(permissions, council_role.permissions);
assert_eq!(vote_policy, council_role.vote_policy);
}

#[test]
fn test_remove_role() {
let council = vec![accounts(0), accounts(1)];
let mut policy = default_policy(council);

let council_role = policy.internal_get_role(&String::from("council"));
assert!(council_role.is_some());
assert_eq!(2, policy.roles.len());

policy.remove_role(&String::from("council"));

let council_role = policy.internal_get_role(&String::from("council"));
assert!(council_role.is_none());
assert_eq!(1, policy.roles.len());
}

#[test]
fn test_update_default_vote_policy() {
let council = vec![accounts(0), accounts(1)];
let mut policy = default_policy(council);

assert_eq!(
WeightKind::RoleWeight,
policy.default_vote_policy.weight_kind
);
assert_eq!(U128(0), policy.default_vote_policy.quorum);
assert_eq!(
WeightOrRatio::Ratio(1, 2),
policy.default_vote_policy.threshold
);

let new_default_vote_policy = VotePolicy {
weight_kind: WeightKind::TokenWeight,
quorum: U128(100),
threshold: WeightOrRatio::Ratio(1, 4),
};
policy.update_default_vote_policy(&new_default_vote_policy);
assert_eq!(
new_default_vote_policy.weight_kind,
policy.default_vote_policy.weight_kind
);
assert_eq!(
new_default_vote_policy.quorum,
policy.default_vote_policy.quorum
);
assert_eq!(
new_default_vote_policy.threshold,
policy.default_vote_policy.threshold
);
}

#[test]
fn test_update_parameters() {
let council = vec![accounts(0), accounts(1)];
let mut policy = default_policy(council);

assert_eq!(U128(10u128.pow(24)), policy.proposal_bond);
assert_eq!(
U64::from(1_000_000_000 * 60 * 60 * 24 * 7),
policy.proposal_period
);
assert_eq!(U128(10u128.pow(24)), policy.bounty_bond);
assert_eq!(
U64::from(1_000_000_000 * 60 * 60 * 24),
policy.bounty_forgiveness_period
);

let new_parameters = PolicyParameters {
proposal_bond: Some(U128(10u128.pow(26))),
proposal_period: None,
bounty_bond: None,
bounty_forgiveness_period: Some(U64::from(1_000_000_000 * 60 * 60 * 24 * 5)),
};
policy.update_parameters(&new_parameters);
assert_eq!(U128(10u128.pow(26)), policy.proposal_bond);
assert_eq!(
U64::from(1_000_000_000 * 60 * 60 * 24 * 7),
policy.proposal_period
);
assert_eq!(U128(10u128.pow(24)), policy.bounty_bond);
assert_eq!(
U64::from(1_000_000_000 * 60 * 60 * 24 * 5),
policy.bounty_forgiveness_period
);
}
}
49 changes: 49 additions & 0 deletions sputnikdao2/src/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ pub struct ActionCall {
gas: U64,
}

/// Function call arguments.
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Clone, Debug))]
#[serde(crate = "near_sdk::serde")]
pub struct PolicyParameters {
pub proposal_bond: Option<U128>,
pub proposal_period: Option<U64>,
pub bounty_bond: Option<U128>,
pub bounty_forgiveness_period: Option<U64>,
}

/// Kinds of proposals, doing different action.
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Clone, Debug))]
Expand All @@ -50,6 +61,14 @@ pub enum ProposalKind {
ChangeConfig { config: Config },
/// Change the full policy.
ChangePolicy { policy: VersionedPolicy },
/// Add new role to the policy. If the role already exists, update it. This is short cut to updating the whole policy.
ChangePolicyAddOrUpdateRole { role: RolePermission },
/// Remove role from the policy. This is short cut to updating the whole policy.
ChangePolicyRemoveRole { role: String },
/// Update the default vote policy from the policy. This is short cut to updating the whole policy.
ChangePolicyUpdateDefaultVotePolicy { vote_policy: VotePolicy },
/// Update the parameters from the policy. This is short cut to updating the whole policy.
ChangePolicyUpdateParameters { parameters: PolicyParameters },
/// Add member to given role in the policy. This is short cut to updating the whole policy.
AddMemberToRole { member_id: AccountId, role: String },
/// Remove member to given role in the policy. This is short cut to updating the whole policy.
Expand Down Expand Up @@ -100,6 +119,12 @@ impl ProposalKind {
match self {
ProposalKind::ChangeConfig { .. } => "config",
ProposalKind::ChangePolicy { .. } => "policy",
ProposalKind::ChangePolicyAddOrUpdateRole { .. } => "policy_add_or_update_role",
ProposalKind::ChangePolicyRemoveRole { .. } => "policy_remove_role",
ProposalKind::ChangePolicyUpdateDefaultVotePolicy { .. } => {
"policy_update_default_vote_policy"
}
ProposalKind::ChangePolicyUpdateParameters { .. } => "policy_update_parameters",
ProposalKind::AddMemberToRole { .. } => "add_member_to_role",
ProposalKind::RemoveMemberFromRole { .. } => "remove_member_from_role",
ProposalKind::FunctionCall { .. } => "call",
Expand Down Expand Up @@ -287,6 +312,30 @@ impl Contract {
self.policy.set(policy);
PromiseOrValue::Value(())
}
ProposalKind::ChangePolicyAddOrUpdateRole { role } => {
let mut new_policy = policy.clone();
new_policy.add_or_update_role(role);
self.policy.set(&VersionedPolicy::Current(new_policy));
PromiseOrValue::Value(())
}
ProposalKind::ChangePolicyRemoveRole { role } => {
let mut new_policy = policy.clone();
new_policy.remove_role(role);
self.policy.set(&VersionedPolicy::Current(new_policy));
PromiseOrValue::Value(())
}
ProposalKind::ChangePolicyUpdateDefaultVotePolicy { vote_policy } => {
let mut new_policy = policy.clone();
new_policy.update_default_vote_policy(vote_policy);
self.policy.set(&VersionedPolicy::Current(new_policy));
PromiseOrValue::Value(())
}
ProposalKind::ChangePolicyUpdateParameters { parameters } => {
let mut new_policy = policy.clone();
new_policy.update_parameters(parameters);
self.policy.set(&VersionedPolicy::Current(new_policy));
PromiseOrValue::Value(())
}
ProposalKind::AddMemberToRole { member_id, role } => {
let mut new_policy = policy.clone();
new_policy.add_member_to_role(role, &member_id.clone().into());
Expand Down
Binary file modified test-token/res/test_token.wasm
Binary file not shown.

0 comments on commit 2e56755

Please sign in to comment.