Skip to content

Commit

Permalink
Fix logic nft contract, docs update (#69)
Browse files Browse the repository at this point in the history
* Update nft token logic, add some documentation
* ensure whitelisted before mint/burn
* allow a user to have at most one token
* init AccessControl module
* add and refactor documentation

Co-authored-by: Maciej Zieliński <[email protected]>

* set fixed version of cargo-expand

Co-authored-by: Maciej Zieliński <[email protected]>
  • Loading branch information
kpob and zie1ony authored May 25, 2022
1 parent 1d8d781 commit aba3ed1
Show file tree
Hide file tree
Showing 16 changed files with 264 additions and 86 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ CARGO_TEST = cargo test --features=test-support --no-default-features

prepare:
rustup target add wasm32-unknown-unknown
cargo install cargo-expand
cargo install cargo-expand --version 1.0.17

build-proxy-getter:
$(CARGO_BUILD) -p casper-dao-utils --bin getter_proxy
Expand Down
18 changes: 9 additions & 9 deletions dao-contracts/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use delegate::delegate;

#[casper_contract_interface]
pub trait AdminContractInterface {
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::init())
fn init(&mut self, variable_repo: Address, reputation_token: Address);

/// Creates new admin voting.
Expand All @@ -31,21 +31,21 @@ pub trait AdminContractInterface {
address: Address,
stake: U256,
);
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::vote())
fn vote(&mut self, voting_id: VotingId, choice: Choice, stake: U256);
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::finish_voting())
fn finish_voting(&mut self, voting_id: VotingId);
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_dust_amount())
fn get_dust_amount(&self) -> U256;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_variable_repo_address())
fn get_variable_repo_address(&self) -> Address;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_reputation_token_address())
fn get_reputation_token_address(&self) -> Address;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_voting())
fn get_voting(&self, voting_id: U256) -> Option<Voting>;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_ballot())
fn get_ballot(&self, voting_id: U256, address: Address) -> Option<Ballot>;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_voter())
fn get_voter(&self, voting_id: U256, at: u32) -> Option<Address>;
}

Expand Down
80 changes: 80 additions & 0 deletions dao-contracts/src/dao_nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,99 @@ use casper_dao_modules::AccessControl;
use casper_dao_utils::{
casper_contract::unwrap_or_revert::UnwrapOrRevert,
casper_dao_macros::{casper_contract_interface, Instance},
casper_env::{self, caller},
Address, Error, Mapping,
};
use casper_types::U256;
use delegate::delegate;

#[casper_contract_interface]
pub trait DaoOwnedNftContractInterface {
/// Contract constructor.
///
/// Initializes modules. Sets the deployer as the owner.
///
/// See [MetadataERC721](MetadataERC721::init()), [AccessControl](AccessControl::init())
fn init(&mut self, name: String, symbol: String, base_uri: TokenUri);
/// Change ownership of the contract. Transfer the ownership to the `owner`. Only current owner
/// is permited to call this method.
///
/// See [AccessControl](AccessControl::change_ownership())
fn change_ownership(&mut self, owner: Address);
/// Add new address to the whitelist.
///
/// See [AccessControl](AccessControl::add_to_whitelist())
fn add_to_whitelist(&mut self, address: Address);
/// Remove address from the whitelist.
///
/// See [AccessControl](AccessControl::remove_from_whitelist())
fn remove_from_whitelist(&mut self, address: Address);
/// Returns the address of the current owner.
fn get_owner(&self) -> Option<Address>;
/// Checks whether the given address is added to the whitelist.
fn is_whitelisted(&self, address: Address) -> bool;
/// Returns a descriptive name for a collection of tokens in this contract
fn name(&self) -> String;
/// Gets an abbreviated name for tokens in this contract
fn symbol(&self) -> String;
/// Returns the address of the owner of the token.
///
/// If the given `token_id` does not exist the None value is returned.
fn owner_of(&self, token_id: TokenId) -> Option<Address>;
/// Returns a token id for the given the `address`.
///
/// If the `owner` does not own any token the None value is returned.
fn token_id(&self, address: Address) -> Option<TokenId>;
/// Returns the number of tokens owned by `owner`.
fn balance_of(&self, owner: Address) -> U256;
/// Returns the total number of tokens.
fn total_supply(&self) -> U256;
/// Returns a distinct Uniform Resource Identifier (URI) for a given asset.
fn token_uri(&self, token_id: TokenId) -> TokenUri;
/// Returns a URI prefix that is used by all the assets.
fn base_uri(&self) -> TokenUri;
/// Creates a new token with the given id and transfers it to a new owner.
/// Increments the total supply and the balance of the `to` address.
///
/// # Note
/// Only whitelisted addresses are permited to call this
/// method.
///
/// Each user is entitled to own only one token.
///
/// # Errors
/// Throws [`TokenAlreadyExists`](Error::TokenAlreadyExists) if a token with
/// the `token_id` has been minted already.
///
/// Throws [`UserAlreadyOwnsToken`](Error::UserAlreadyOwnsToken) if the `to` address
/// already owns a token.
///
/// # Events
/// Emits [`Transfer`](casper_dao_erc721::events::Transfer) event when minted successfully.
fn mint(&mut self, to: Address, token_id: TokenId);
/// Burns a token with the given id. Decrements the balance of the token owner
/// and decrements the total supply.
///
/// # Errors
/// Throws [`NotWhitelisted`](casper_dao_utils::Error::NotWhitelisted) if caller
/// is not whitelisted.
///
/// # Events
/// Emits [`Burn`](casper_dao_modules::events::Burn) event.
fn burn(&mut self, token_id: TokenId);
/// Change or confirm the approved address for a token with the given id.
fn approve(&mut self, approved: Option<Address>, token_id: TokenId);
/// Enables or disables approval for a third party (`operator`) to manage
/// all of the caller assets.
fn set_approval_for_all(&mut self, operator: Address, approved: bool);
}

/// Dao Owned Nft contract acts like an erc-721 token and derives most of erc-721 standards from
/// [ERC721Token](ERC721Token) module.
///
/// Dao Owned Nft token is mintable and burnable but the caller needs to have permissions to perform those actions.
///
/// For details see [DaoOwnedNftContractInterface](DaoOwnedNftContractInterface)
#[derive(Instance)]
pub struct DaoOwnedNftContract {
token: ERC721Token,
Expand All @@ -42,7 +108,9 @@ pub struct DaoOwnedNftContract {

impl DaoOwnedNftContractInterface for DaoOwnedNftContract {
fn init(&mut self, name: String, symbol: String, base_uri: TokenUri) {
let deployer = caller();
self.metadata.init(name, symbol, base_uri);
self.access_control.init(deployer);
}

delegate! {
Expand Down Expand Up @@ -74,11 +142,15 @@ impl DaoOwnedNftContractInterface for DaoOwnedNftContract {
}

fn mint(&mut self, to: Address, token_id: TokenId) {
self.access_control.ensure_whitelisted();
self.assert_does_not_own_token(&to);

MintableERC721::mint(&mut self.token, to, token_id);
self.tokens.set(&to, Some(token_id));
}

fn burn(&mut self, token_id: TokenId) {
self.access_control.ensure_whitelisted();
let owner = self
.token
.owner_of(token_id)
Expand All @@ -91,3 +163,11 @@ impl DaoOwnedNftContractInterface for DaoOwnedNftContract {
self.tokens.get(&address).unwrap_or(None)
}
}

impl DaoOwnedNftContract {
fn assert_does_not_own_token(&self, address: &Address) {
if self.tokens.get(address).is_some() {
casper_env::revert(Error::UserAlreadyOwnsToken)
}
}
}
52 changes: 40 additions & 12 deletions dao-contracts/src/kyc_voter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,50 @@ use delegate::delegate;

#[casper_contract_interface]
pub trait KycVoterContractInterface {
/// Contract constructor
///
/// Initializes modules.
///
/// See [GovernanceVoting](GovernanceVoting::init()), [KycInfo](KycInfo::init())
fn init(&mut self, variable_repo: Address, reputation_token: Address, kyc_token: Address);
// Require no voting for a given `address` is on.
// Precondition: KycNft.balance_of(address_to_onboard) == 0;
// Action: KycNft.mint(address_to_onboard, next_token_id)
fn create_voting(&mut self, address_to_onboard: Address, document_hash: U256, stake: U256);
/// Creates new kyc voting. Once the voting passes a kyc token is minted to the `subject_address`.
///
/// # Prerequisites
///
/// * no voting on the given `subject_address` is in progress,
/// * `subject_address` does not own a kyc token.
///
/// # Note
///
/// `subject_address` - [address](Address) of a user to be verified.
/// `document_hash` - a hash of a document that vefify the user. The hash is used as an id of a freshly minted kyc token.
/// `subject_address` - an [Address](Address) to be on/offboarded.
fn create_voting(&mut self, subject_address: Address, document_hash: U256, stake: U256);
/// see [GovernanceVoting](GovernanceVoting::vote())
fn vote(&mut self, voting_id: VotingId, choice: Choice, stake: U256);
/// see [GovernanceVoting](GovernanceVoting::finish_voting())
fn finish_voting(&mut self, voting_id: VotingId);
/// see [GovernanceVoting](GovernanceVoting::get_dust_amount())
fn get_dust_amount(&self) -> U256;
/// see [GovernanceVoting](GovernanceVoting::get_variable_repo_address())
fn get_variable_repo_address(&self) -> Address;
/// see [GovernanceVoting](GovernanceVoting::get_reputation_token_address())
fn get_reputation_token_address(&self) -> Address;
/// see [GovernanceVoting](GovernanceVoting::get_voting())
fn get_voting(&self, voting_id: U256) -> Option<Voting>;
/// see [GovernanceVoting](GovernanceVoting::get_ballot())
fn get_ballot(&self, voting_id: U256, address: Address) -> Option<Ballot>;
/// see [GovernanceVoting](GovernanceVoting::get_voter())
fn get_voter(&self, voting_id: U256, at: u32) -> Option<Address>;
/// see [KycInfo](KycInfo::get_kyc_token_address())
fn get_kyc_token_address(&self) -> Address;
fn get_voting(&self, voting_id: VotingId) -> Option<Voting>;
fn get_ballot(&self, voting_id: VotingId, address: Address) -> Option<Ballot>;
fn get_voter(&self, voting_id: VotingId, at: u32) -> Option<Address>;
}

/// KycVoterContract
///
/// It is responsible for managing kyc tokens (see [DaoOwnedNftContract](crate::DaoOwnedNftContract).
///
/// When the voting passes, a kyc token is minted.
#[derive(Instance)]
pub struct KycVoterContract {
kyc: KycInfo,
Expand Down Expand Up @@ -56,22 +84,22 @@ impl KycVoterContractInterface for KycVoterContract {
}
}

fn create_voting(&mut self, address_to_onboard: Address, document_hash: U256, stake: U256) {
self.assert_no_ongoing_voting(&address_to_onboard);
self.assert_not_kyced(&address_to_onboard);
fn create_voting(&mut self, subject_address: Address, document_hash: U256, stake: U256) {
self.assert_no_ongoing_voting(&subject_address);
self.assert_not_kyced(&subject_address);

let creator = caller();
let contract_to_call = self.get_kyc_token_address();
let runtime_args = runtime_args! {
consts::ARG_TO => address_to_onboard,
consts::ARG_TO => subject_address,
consts::ARG_TOKEN_ID => document_hash,
};
let entry_point = consts::EP_MINT.to_string();

self.voting
.create_voting(creator, stake, contract_to_call, entry_point, runtime_args);

self.kyc.set_voting(&address_to_onboard);
self.kyc.set_voting(&subject_address);
}

fn vote(&mut self, voting_id: VotingId, choice: Choice, stake: U256) {
Expand Down
55 changes: 43 additions & 12 deletions dao-contracts/src/onboarding_voter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,66 @@ use delegate::delegate;

#[casper_contract_interface]
pub trait OnboardingVoterContractInterface {
/// Contract constructor
///
/// Initializes modules.
///
/// See [GovernanceVoting](GovernanceVoting::init()), [KycInfo](KycInfo::init()), [OnboardingInfo](OnboardingInfo::init())
fn init(
&mut self,
variable_repo: Address,
reputation_token: Address,
kyc_token: Address,
va_token: Address,
);

// - Require no voting for a given `address` is in progress.
// For Adding new VA:
// - Check if VA is not onboarderd.
// - Check if `address` is KYCed.
// - Check if `address` has positive reputation amount.
// For Removing existing VA:
// - Check if VA is already onboarderd.
// - Check if `address` has positive reputation amount.
/// Creates new Onboarding\Offboarding voting.
///
/// # Prerequisites
///
/// * no voting on the given `subject_address` is in progress.
///
/// To onboard a new VA:
/// * `subject_address` must not be already onboarded,
/// * `subject_address` must be KYCd,
/// * `subject_address` must have a positive reputation token amount.
///
/// To offboard a VA:
/// * `subject_address` must be already onboarded,
/// * `subject_address` must have a positive reputation token amount.
///
/// # Note
///
/// `action` - the mode of newly created voting can be either onboarding ([Add](OnboardingAction::Add)) or offboarding ([Remove](OnboardingAction::Remove)).
///
/// `subject_address` - an [Address](Address) to be on/offboarded.
fn create_voting(&mut self, action: OnboardingAction, subject_address: Address, stake: U256);
/// see [GovernanceVoting](GovernanceVoting::vote())
fn vote(&mut self, voting_id: VotingId, choice: Choice, stake: U256);
/// see [GovernanceVoting](GovernanceVoting::finish_voting())
fn finish_voting(&mut self, voting_id: VotingId);
/// see [GovernanceVoting](GovernanceVoting::get_dust_amount())
fn get_dust_amount(&self) -> U256;
/// see [GovernanceVoting](GovernanceVoting::get_variable_repo_address())
fn get_variable_repo_address(&self) -> Address;
/// see [GovernanceVoting](GovernanceVoting::get_reputation_token_address())
fn get_reputation_token_address(&self) -> Address;
/// see [GovernanceVoting](GovernanceVoting::get_voting())
fn get_voting(&self, voting_id: U256) -> Option<Voting>;
/// see [GovernanceVoting](GovernanceVoting::get_ballot())
fn get_ballot(&self, voting_id: U256, address: Address) -> Option<Ballot>;
/// see [GovernanceVoting](GovernanceVoting::get_voter())
fn get_voter(&self, voting_id: U256, at: u32) -> Option<Address>;
/// see [KycInfo](KycInfo::get_kyc_token_address())
fn get_kyc_token_address(&self) -> Address;
/// see [OnboardingInfo](OnboardingInfo::get_va_token_address())
fn get_va_token_address(&self) -> Address;
fn get_voting(&self, voting_id: VotingId) -> Option<Voting>;
fn get_ballot(&self, voting_id: VotingId, address: Address) -> Option<Ballot>;
fn get_voter(&self, voting_id: VotingId, at: u32) -> Option<Address>;
}

/// OnboardingVoterContract
///
/// It is responsible for managing va tokens (see [DaoOwnedNftContract](crate::DaoOwnedNftContract)).
///
/// When the voting passes, a va token is minted (onboarding) or burned (offboarding).
#[derive(Instance)]
pub struct OnboardingVoterContract {
onboarding: OnboardingInfo,
Expand Down
18 changes: 9 additions & 9 deletions dao-contracts/src/repo_voter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use delegate::delegate;

#[casper_contract_interface]
pub trait RepoVoterContractInterface {
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::init())
fn init(&mut self, variable_repo: Address, reputation_token: Address);
/// Creates new RepoVoter voting.
///
Expand All @@ -26,21 +26,21 @@ pub trait RepoVoterContractInterface {
activation_time: Option<u64>,
stake: U256,
);
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::vote())
fn vote(&mut self, voting_id: VotingId, choice: Choice, stake: U256);
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::finish_voting())
fn finish_voting(&mut self, voting_id: VotingId);
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_dust_amount())
fn get_dust_amount(&self) -> U256;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_variable_repo_address())
fn get_variable_repo_address(&self) -> Address;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_reputation_token_address())
fn get_reputation_token_address(&self) -> Address;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_voting())
fn get_voting(&self, voting_id: U256) -> Option<Voting>;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_ballot())
fn get_ballot(&self, voting_id: U256, address: Address) -> Option<Ballot>;
/// see [GovernanceVoting](GovernanceVoting)
/// see [GovernanceVoting](GovernanceVoting::get_voter())
fn get_voter(&self, voting_id: U256, at: u32) -> Option<Address>;
}

Expand Down
Loading

0 comments on commit aba3ed1

Please sign in to comment.