Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate hooks #4

Merged
merged 29 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions astra/src/bounties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ mod tests {
let mut contract = Contract::new(
Config::test_config(),
VersionedPolicy::Default(vec![accounts(1)]),
accounts(1)
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
);
add_bounty(&mut context, &mut contract, 2);

Expand Down Expand Up @@ -303,6 +304,7 @@ mod tests {
let mut contract = Contract::new(
Config::test_config(),
VersionedPolicy::Default(vec![accounts(1)]),
accounts(1)
);
let id = add_bounty(&mut context, &mut contract, 1);
contract.bounty_claim(id, U64::from(500));
Expand Down
137 changes: 134 additions & 3 deletions astra/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use near_sdk::{
env, ext_contract, near_bindgen, AccountId, Balance, BorshStorageKey, CryptoHash,
PanicOnDefault, Promise, PromiseResult, PromiseOrValue,
};
use policy::UserInfo;

pub use crate::bounties::{Bounty, BountyClaim, VersionedBounty};
pub use crate::policy::{
Expand Down Expand Up @@ -78,12 +79,15 @@ pub struct Contract {

/// Large blob storage.
pub blobs: LookupMap<CryptoHash, AccountId>,

/// Trust address
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
pub trust: AccountId,
}

#[near_bindgen]
impl Contract {
#[init]
pub fn new(config: Config, policy: VersionedPolicy) -> Self {
pub fn new(config: Config, policy: VersionedPolicy, trust: AccountId) -> Self {
let this = Self {
config: LazyOption::new(StorageKeys::Config, Some(&config)),
policy: LazyOption::new(StorageKeys::Policy, Some(&policy.upgrade())),
Expand All @@ -98,6 +102,7 @@ impl Contract {
bounty_claims_count: LookupMap::new(StorageKeys::BountyClaimCounts),
blobs: LookupMap::new(StorageKeys::Blobs),
locked_amount: 0,
trust,
};
internal_set_factory_info(&FactoryInfo {
factory_id: env::predecessor_account_id(),
Expand Down Expand Up @@ -137,6 +142,58 @@ impl Contract {
pub fn get_factory_info(&self) -> FactoryInfo {
internal_get_factory_info()
}

/// Veto proposal hook
/// Check for authorities and remove proposal
/// * `id`: proposal id
pub fn veto_hook(&mut self, id: u64) {
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
let policy = self.policy.get().unwrap().to_policy();
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
let res =
policy.can_execute_action(UserInfo {
amount: 0u128,
account_id: env::predecessor_account_id(),
}, &ProposalKind::VetoProposal{}, &Action::VetoProposal
);
assert!(res.1, "not authorized");

// Check if the proposal exist and is not finalized
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
let proposal: Proposal = self.proposals.get(&id).expect("Proposal doesn't exist").into();
if proposal.status == ProposalStatus::InProgress || proposal.status == ProposalStatus::Failed {
self.proposals.remove(&id);
} else {
panic!("proposal finalized")
}
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
}

/// Dissolve proposal hook
/// Check for authorities and remove all the members
/// Transfer funds to trust
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
pub fn dissolve_hook(&mut self) {
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
let policy = self.policy.get().unwrap().to_policy();
let res =
policy.can_execute_action(UserInfo {
amount: 0u128,
account_id: env::predecessor_account_id(),
}, &ProposalKind::Dissolve{}, &Action::Dissolve
);
assert!(res.1, "not authorized");

// All the members are removed so operations are in freeze state
let mut policy = self.policy.get().unwrap().to_policy();
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
// Return bond amounts
for prop_id in 0..self.last_proposal_id {
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
let prop: Proposal = self.proposals.get(&prop_id).unwrap().into();
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
if prop.status == ProposalStatus::InProgress {
self.internal_return_bonds(&policy, &prop);
}
}
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved

policy.roles = vec![];
self.policy.set(&VersionedPolicy::Current(policy));

let funds = env::account_balance() - self.locked_amount;
Promise::new(self.trust.clone()).transfer(funds);
}
}

/// Stores attached data into blob store and returns hash of it.
Expand Down Expand Up @@ -174,7 +231,7 @@ pub extern "C" fn store_blob() {
#[cfg(test)]
mod tests {
use near_sdk::test_utils::{accounts, VMContextBuilder};
use near_sdk::testing_env;
use near_sdk::{testing_env, VMContext};
use near_units::parse_near;

use crate::proposals::ProposalStatus;
Expand All @@ -194,13 +251,33 @@ mod tests {
})
}

fn setup_hook_proposal() -> (VMContext, Contract, u64) {
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
let mut context = VMContextBuilder::new();
testing_env!(context.predecessor_account_id(accounts(1)).build());
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
let mut policy = VersionedPolicy::Default(vec![accounts(2)]).upgrade();
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
policy.to_policy_mut().roles[1]
.permissions
.insert("*:VetoProposal".to_string());
policy.to_policy_mut().roles[1]
.permissions
.insert("*:Dissolve".to_string());
let mut contract = Contract::new(
Config::test_config(),
policy,
accounts(1)
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
);
let id = create_proposal(&mut context, &mut contract);
(context.build(), contract, id)
}

#[test]
fn test_basics() {
let mut context = VMContextBuilder::new();
testing_env!(context.predecessor_account_id(accounts(1)).build());
let mut contract = Contract::new(
Config::test_config(),
VersionedPolicy::Default(vec![accounts(1)]),
accounts(1)
);
let id = create_proposal(&mut context, &mut contract);
assert_eq!(contract.get_proposal(id).proposal.description, "test");
Expand Down Expand Up @@ -246,6 +323,7 @@ mod tests {
let mut contract = Contract::new(
Config::test_config(),
VersionedPolicy::Default(vec![accounts(1)]),
accounts(1)
);
let id = create_proposal(&mut context, &mut contract);
assert_eq!(contract.get_proposal(id).proposal.description, "test");
Expand All @@ -260,7 +338,7 @@ mod tests {
policy.to_policy_mut().roles[1]
.permissions
.insert("*:RemoveProposal".to_string());
let mut contract = Contract::new(Config::test_config(), policy);
let mut contract = Contract::new(Config::test_config(), policy, accounts(1));
let id = create_proposal(&mut context, &mut contract);
assert_eq!(contract.get_proposal(id).proposal.description, "test");
contract.act_proposal(id, Action::RemoveProposal, None);
Expand All @@ -274,6 +352,7 @@ mod tests {
let mut contract = Contract::new(
Config::test_config(),
VersionedPolicy::Default(vec![accounts(1)]),
accounts(1)
);
let id = create_proposal(&mut context, &mut contract);
testing_env!(context
Expand All @@ -290,6 +369,7 @@ mod tests {
let mut contract = Contract::new(
Config::test_config(),
VersionedPolicy::Default(vec![accounts(1), accounts(2)]),
accounts(1)
);
let id = create_proposal(&mut context, &mut contract);
contract.act_proposal(id, Action::VoteApprove, None);
Expand All @@ -303,6 +383,7 @@ mod tests {
let mut contract = Contract::new(
Config::test_config(),
VersionedPolicy::Default(vec![accounts(1)]),
accounts(1)
);
testing_env!(context.attached_deposit(parse_near!("1 N")).build());
let id = contract.add_proposal(ProposalInput {
Expand All @@ -326,6 +407,7 @@ mod tests {
let mut contract = Contract::new(
Config::test_config(),
VersionedPolicy::Default(vec![accounts(1)]),
accounts(1)
);
testing_env!(context.attached_deposit(parse_near!("1 N")).build());
let _id = contract.add_proposal(ProposalInput {
Expand All @@ -335,4 +417,53 @@ mod tests {
},
});
}

#[test]
#[should_panic(expected = "ERR_NO_PROPOSAL")]
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
fn test_veto_hook() {
let (mut context, mut contract, id)= setup_hook_proposal();
assert_eq!(contract.get_proposal(id).id, id);

context.predecessor_account_id = accounts(2);
testing_env!(context);
contract.veto_hook(id);

contract.get_proposal(id);
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
}

#[test]
#[should_panic(expected = "not authorized")]
fn test_veto_hook_unauthorised() {
let (_, mut contract, id)= setup_hook_proposal();
assert_eq!(contract.get_proposal(id).id, id);
contract.veto_hook(id);
}

#[test]
#[should_panic(expected = "ERR_PERMISSION_DENIED")]
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
fn test_dissolve_hook() {
let (mut context, mut contract, id)= setup_hook_proposal();
assert_eq!(contract.get_proposal(id).id, id);

let mut res = contract.policy.get().unwrap().to_policy();
assert_eq!(res.roles.is_empty(), false);

context.predecessor_account_id = accounts(2);
testing_env!(context.clone());
contract.dissolve_hook();
res = contract.policy.get().unwrap().to_policy();
assert_eq!(res.roles.is_empty(), true);

context.predecessor_account_id = accounts(1);
context.attached_deposit = parse_near!("1 N");
testing_env!(context);

contract.add_proposal(ProposalInput {
description: "test".to_string(),
kind: ProposalKind::AddMemberToRole {
member_id: accounts(2),
role: "missing".to_string(),
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
},
});
}
}
6 changes: 6 additions & 0 deletions astra/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub enum RoleKind {
Member(U128),
/// Set of accounts.
Group(HashSet<AccountId>),
/// House accounts
/// Note: Hook for houses will only work if change policy is disabled.
House(HashSet<AccountId>),
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
}

impl RoleKind {
Expand All @@ -28,6 +31,7 @@ impl RoleKind {
RoleKind::Everyone => true,
RoleKind::Member(amount) => user.amount >= amount.0,
RoleKind::Group(accounts) => accounts.contains(&user.account_id),
RoleKind::House(accounts) => accounts.contains(&user.account_id),
}
}

Expand Down Expand Up @@ -410,6 +414,8 @@ impl Policy {
}
}
RoleKind::Member(_) => total_supply,
// Skip house
RoleKind::House(_) => continue,
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
};
let threshold = std::cmp::max(
vote_policy.quorum.0,
Expand Down
16 changes: 15 additions & 1 deletion astra/src/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ pub enum ProposalKind {
ChangePolicyUpdateDefaultVotePolicy { vote_policy: VotePolicy },
/// Update the parameters from the policy. This is short cut to updating the whole policy.
ChangePolicyUpdateParameters { parameters: PolicyParameters },
/// Veto hook for house
VetoProposal {},
/// Dissolve hook for house
Dissolve {}
}

impl ProposalKind {
Expand All @@ -138,6 +142,8 @@ impl ProposalKind {
"policy_update_default_vote_policy"
}
ProposalKind::ChangePolicyUpdateParameters { .. } => "policy_update_parameters",
ProposalKind::VetoProposal { } => "veto_proposal",
ProposalKind::Dissolve { } => "dissolve",
}
}
}
Expand Down Expand Up @@ -285,7 +291,7 @@ impl Contract {
}
}

fn internal_return_bonds(&mut self, policy: &Policy, proposal: &Proposal) -> Promise {
pub(crate) fn internal_return_bonds(&mut self, policy: &Policy, proposal: &Proposal) -> Promise {
match &proposal.kind {
ProposalKind::BountyDone { .. } => {
self.locked_amount -= policy.bounty_bond.0;
Expand Down Expand Up @@ -407,6 +413,12 @@ impl Contract {
self.policy.set(&VersionedPolicy::Current(new_policy));
PromiseOrValue::Value(())
}
ProposalKind::VetoProposal { } => {
panic!("not allowed to veto proposal")
}
ProposalKind::Dissolve { } => {
panic!("not allowed to dissolve dao")
}
};
match result {
PromiseOrValue::Promise(promise) => promise
Expand Down Expand Up @@ -609,6 +621,8 @@ impl Contract {
true
}
Action::MoveToHub => false,
Action::VetoProposal => panic!("Operation not allowed"),
Action::Dissolve => panic!("Operation not allowed"),
};
if update {
self.proposals
Expand Down
4 changes: 4 additions & 0 deletions astra/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ pub enum Action {
Finalize,
/// Move a proposal to the hub to shift into another DAO.
MoveToHub,
/// Veto hook for house
VetoProposal,
/// Dissovle hook for house
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
Dissolve,
}

impl Action {
Expand Down
4 changes: 2 additions & 2 deletions astra/tests/test_general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async fn test_large_policy() -> anyhow::Result<()> {
metadata: Base64VecU8(vec![]),
};
let root_near_account: AccountId = root.id().parse().unwrap();
let mut policy = default_policy(vec![root_near_account]);
let mut policy = default_policy(vec![root_near_account.clone()]);
const NO_OF_COUNCILS: u32 = 10;
const USERS_PER_COUNCIL: u32 = 100;
for council_no in 0..NO_OF_COUNCILS {
Expand Down Expand Up @@ -65,7 +65,7 @@ async fn test_large_policy() -> anyhow::Result<()> {
policy.add_or_update_role(&role);
}

let params = json!({ "config": config, "policy": policy })
let params = json!({ "config": config, "policy": policy, "trust": root_near_account})
.to_string()
.into_bytes();

Expand Down
4 changes: 2 additions & 2 deletions astra/tests/test_upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ async fn test_upgrade_using_factory() -> anyhow::Result<()> {
};
let root_near_account: AccountId = root.id().parse().unwrap();

let policy = VersionedPolicy::Default(vec![root_near_account]);
let params = json!({ "config": config, "policy": policy })
let policy = VersionedPolicy::Default(vec![root_near_account.clone()]);
let params = json!({ "config": config, "policy": policy, "trust": root_near_account})
.to_string()
.into_bytes();

Expand Down
3 changes: 2 additions & 1 deletion astra/tests/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ pub async fn setup_dao() -> anyhow::Result<(Account, Contract, Worker<Sandbox>)>
let res1 = dao_contract
.call("new")
.args_json(json!({
"config": config, "policy": VersionedPolicy::Default(vec![root_near_account])
"config": config, "policy": VersionedPolicy::Default(vec![root_near_account.clone()]),
"trust": root_near_account
}))
.max_gas()
.transact().await?;
Expand Down
Loading