Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into factory-update
Browse files Browse the repository at this point in the history
  • Loading branch information
ilblackdragon committed Jan 11, 2022
2 parents 156914f + 2a8e43a commit 869a3a2
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 23 deletions.
Binary file modified sputnikdao-factory2/res/sputnikdao_factory2.wasm
Binary file not shown.
8 changes: 8 additions & 0 deletions sputnikdao2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ Blob lifecycle:

Blob can be removed only by the original storer.

## Upgradability

There are two major ways to upgrade the DAO:
- Self upgrade by storing blob on the DAO contract and then voting to UpgradeSelf
- Upgrade from the factory - factory stores new contract and then if allowed upgrades DAO by calling `upgrade(code)`.

DAO contracts can explicitly vote to disable factory auto upgrades and require to pull the upgrade themself from factory.

## Testing

To test the sputnik2 DAO you will need a testnet account. If you don't have one yet create it in https://wallet.testnet.near.org/.
Expand Down
Binary file modified sputnikdao2/res/sputnikdao2.wasm
Binary file not shown.
21 changes: 10 additions & 11 deletions sputnikdao2/src/bounties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ impl Contract {
success: bool,
) -> PromiseOrValue<()> {
let bounty: Bounty = self.bounties.get(&id).expect("ERR_NO_BOUNTY").into();
let (claims, claim_idx) = self.internal_get_claims(id, &receiver_id);
self.internal_remove_claim(id, claims, claim_idx);
self.internal_remove_claim(id, receiver_id);
if success {
self.internal_payout(
&bounty.token,
Expand Down Expand Up @@ -131,17 +130,17 @@ impl Contract {
self.locked_amount += env::attached_deposit();
}

/// Removes given claims from this bounty and user's claims.
fn internal_remove_claim(&mut self, id: u64, mut claims: Vec<BountyClaim>, claim_idx: usize) {
/// Remove the claim of `claimer_id` from this bounty.
fn internal_remove_claim(&mut self, bounty_id: u64, claimer_id: &AccountId) {
let (mut claims, claim_idx) = self.internal_get_claims(bounty_id, claimer_id);
claims.remove(claim_idx);
if claims.len() == 0 {
self.bounty_claimers.remove(&env::predecessor_account_id());
self.bounty_claimers.remove(claimer_id);
} else {
self.bounty_claimers
.insert(&env::predecessor_account_id(), &claims);
self.bounty_claimers.insert(claimer_id, &claims);
}
let count = self.bounty_claims_count.get(&id).unwrap() - 1;
self.bounty_claims_count.insert(&id, &count);
let count = self.bounty_claims_count.get(&bounty_id).unwrap() - 1;
self.bounty_claims_count.insert(&bounty_id, &count);
}

fn internal_get_claims(&mut self, id: u64, sender_id: &AccountId) -> (Vec<BountyClaim>, usize) {
Expand All @@ -165,7 +164,7 @@ impl Contract {
assert!(!claims[claim_idx].completed, "ERR_BOUNTY_CLAIM_COMPLETED");
if env::block_timestamp() > claims[claim_idx].start_time.0 + claims[claim_idx].deadline.0 {
// Expired. Nothing to do.
self.internal_remove_claim(id, claims, claim_idx);
self.internal_remove_claim(id, &sender_id);
} else {
// Still under deadline. Only the user themself can call this.
assert_eq!(
Expand Down Expand Up @@ -201,7 +200,7 @@ impl Contract {
.transfer(policy.bounty_bond.0)
.into()
};
self.internal_remove_claim(id, claims, claim_idx);
self.internal_remove_claim(id, &env::predecessor_account_id());
result
}
}
Expand Down
3 changes: 2 additions & 1 deletion sputnikdao2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ use near_sdk::{
PanicOnDefault, Promise, PromiseResult,
};

use crate::bounties::{Bounty, BountyClaim, VersionedBounty};
pub use crate::bounties::{Bounty, BountyClaim, VersionedBounty};
pub use crate::policy::{Policy, RoleKind, RolePermission, VersionedPolicy, VotePolicy};
use crate::proposals::VersionedProposal;
pub use crate::proposals::{Proposal, ProposalInput, ProposalKind, ProposalStatus};
pub use crate::types::{Action, Config};
use crate::upgrade::{internal_get_factory_info, internal_set_factory_info, FactoryInfo};
pub use crate::views::{BountyOutput, ProposalOutput};

mod bounties;
mod delegation;
Expand Down
20 changes: 14 additions & 6 deletions sputnikdao2/src/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,15 @@ impl Contract {
}
}

fn internal_return_bond(&mut self, policy: &Policy, proposal: &Proposal) -> Promise {
fn internal_return_bonds(&mut self, policy: &Policy, proposal: &Proposal) -> Promise {
match &proposal.kind {
ProposalKind::BountyDone { .. } => {
self.locked_amount -= policy.bounty_bond.0;
Promise::new(proposal.proposer.clone()).transfer(policy.bounty_bond.0);
}
_ => {}
}

self.locked_amount -= policy.proposal_bond.0;
Promise::new(proposal.proposer.clone()).transfer(policy.proposal_bond.0)
}
Expand Down Expand Up @@ -358,7 +366,7 @@ impl Contract {
GAS_FOR_FT_TRANSFER,
))
.into(),
PromiseOrValue::Value(()) => self.internal_return_bond(&policy, &proposal).into(),
PromiseOrValue::Value(()) => self.internal_return_bonds(&policy, &proposal).into(),
}
}

Expand All @@ -378,7 +386,7 @@ impl Contract {
}
}
proposal.status = ProposalStatus::Approved;
self.internal_return_bond(&policy, &proposal).into()
self.internal_return_bonds(&policy, &proposal).into()
}

pub(crate) fn internal_callback_proposal_fail(
Expand All @@ -394,11 +402,11 @@ impl Contract {
&mut self,
policy: &Policy,
proposal: &Proposal,
return_bond: bool,
return_bonds: bool,
) -> PromiseOrValue<()> {
if return_bond {
if return_bonds {
// Return bond to the proposer.
self.internal_return_bond(policy, proposal);
self.internal_return_bonds(policy, proposal);
}
match &proposal.kind {
ProposalKind::BountyDone {
Expand Down
124 changes: 121 additions & 3 deletions sputnikdao2/tests/test_general.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::collections::HashMap;

use near_sdk::json_types::U128;
use near_sdk::AccountId;
use near_sdk::{env, AccountId};
use near_sdk_sim::{call, to_yocto, view};

use crate::utils::*;
use sputnik_staking::User;
use sputnikdao2::{
Action, Policy, Proposal, ProposalInput, ProposalKind, ProposalStatus, RoleKind,
RolePermission, VersionedPolicy, VotePolicy,
Action, BountyClaim, BountyOutput, Policy, Proposal, ProposalInput, ProposalKind,
ProposalOutput, ProposalStatus, RoleKind, RolePermission, VersionedPolicy, VotePolicy,
};

mod utils;
Expand Down Expand Up @@ -75,6 +75,124 @@ fn test_multi_council() {
assert_eq!(proposal.status, ProposalStatus::Approved);
}

#[test]
fn test_bounty_workflow() {
let (root, dao) = setup_dao();
let user1 = root.create_user(user(1), to_yocto("1000"));
let user2 = root.create_user(user(2), to_yocto("1000"));

let mut proposal_id = add_bounty_proposal(&root, &dao).unwrap_json::<u64>();
assert_eq!(proposal_id, 0);
call!(
root,
dao.act_proposal(proposal_id, Action::VoteApprove, None)
)
.assert_success();

let bounty_id = view!(dao.get_last_bounty_id()).unwrap_json::<u64>() - 1;
assert_eq!(bounty_id, 0);
assert_eq!(
view!(dao.get_bounty(bounty_id))
.unwrap_json::<BountyOutput>()
.bounty
.times,
3
);

assert_eq!(to_yocto("1000"), user1.account().unwrap().amount);
call!(
user1,
dao.bounty_claim(bounty_id, U64::from(0)),
deposit = to_yocto("1")
)
.assert_success();
assert!(user1.account().unwrap().amount < to_yocto("999"));
assert_eq!(
view!(dao.get_bounty_claims(user1.account_id()))
.unwrap_json::<Vec<BountyClaim>>()
.len(),
1
);
assert_eq!(
view!(dao.get_bounty_number_of_claims(bounty_id)).unwrap_json::<u64>(),
1
);

call!(user1, dao.bounty_giveup(bounty_id)).assert_success();
assert!(user1.account().unwrap().amount > to_yocto("999"));
assert_eq!(
view!(dao.get_bounty_claims(user1.account_id()))
.unwrap_json::<Vec<BountyClaim>>()
.len(),
0
);
assert_eq!(
view!(dao.get_bounty_number_of_claims(bounty_id)).unwrap_json::<u64>(),
0
);

assert_eq!(to_yocto("1000"), user2.account().unwrap().amount);
call!(
user2,
dao.bounty_claim(bounty_id, U64(env::block_timestamp() + 5_000_000_000)),
deposit = to_yocto("1")
)
.assert_success();
assert!(user2.account().unwrap().amount < to_yocto("999"));
assert_eq!(
view!(dao.get_bounty_claims(user2.account_id()))
.unwrap_json::<Vec<BountyClaim>>()
.len(),
1
);
assert_eq!(
view!(dao.get_bounty_number_of_claims(bounty_id)).unwrap_json::<u64>(),
1
);

call!(
user2,
dao.bounty_done(bounty_id, None, "Bounty is done".to_string()),
deposit = to_yocto("1")
)
.assert_success();
assert!(user2.account().unwrap().amount < to_yocto("998"));
proposal_id = view!(dao.get_last_proposal_id()).unwrap_json::<u64>() - 1;
assert_eq!(proposal_id, 1);
assert_eq!(
view!(dao.get_proposal(proposal_id))
.unwrap_json::<ProposalOutput>()
.proposal
.kind
.to_policy_label(),
"bounty_done"
);

call!(
root,
dao.act_proposal(proposal_id, Action::VoteApprove, None)
)
.assert_success();
assert!(user2.account().unwrap().amount > to_yocto("999"));
assert_eq!(
view!(dao.get_bounty_claims(user2.account_id()))
.unwrap_json::<Vec<BountyClaim>>()
.len(),
0
);
assert_eq!(
view!(dao.get_bounty_number_of_claims(bounty_id)).unwrap_json::<u64>(),
0
);
assert_eq!(
view!(dao.get_bounty(bounty_id))
.unwrap_json::<BountyOutput>()
.bounty
.times,
2
);
}

#[test]
fn test_create_dao_and_use_token() {
let (root, dao) = setup_dao();
Expand Down
24 changes: 22 additions & 2 deletions sputnikdao2/tests/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(dead_code)]
pub use near_sdk::json_types::{Base64VecU8, U64};
use near_sdk::{AccountId, Balance};
use near_sdk::{env, AccountId, Balance};
use near_sdk_sim::transaction::ExecutionStatus;
use near_sdk_sim::{
call, deploy, init_simulator, to_yocto, ContractAccount, ExecutionResult, UserAccount,
Expand All @@ -9,7 +9,8 @@ use near_sdk_sim::{
use near_sdk::json_types::U128;
use sputnik_staking::ContractContract as StakingContract;
use sputnikdao2::{
Action, Config, ContractContract as DAOContract, ProposalInput, ProposalKind, VersionedPolicy,
Action, Bounty, Config, ContractContract as DAOContract, ProposalInput, ProposalKind,
VersionedPolicy,
};
use test_token::ContractContract as TestTokenContract;

Expand Down Expand Up @@ -121,6 +122,25 @@ pub fn add_transfer_proposal(
)
}

pub fn add_bounty_proposal(root: &UserAccount, dao: &Contract) -> ExecutionResult {
add_proposal(
root,
dao,
ProposalInput {
description: "test".to_string(),
kind: ProposalKind::AddBounty {
bounty: Bounty {
description: "test bounty".to_string(),
token: None,
amount: U128(to_yocto("10")),
times: 3,
max_deadline: U64(env::block_timestamp() + 10_000_000_000),
},
},
},
)
}

pub fn vote(users: Vec<&UserAccount>, dao: &Contract, proposal_id: u64) {
for user in users.into_iter() {
call!(
Expand Down

0 comments on commit 869a3a2

Please sign in to comment.