diff --git a/Cargo.lock b/Cargo.lock index 095c0284..c72a4fb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2000,6 +2000,36 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +[[package]] +name = "potlock" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "multiversx-sc-scenario", + "num-bigint", +] + +[[package]] +name = "potlock-interact" +version = "0.0.0" +dependencies = [ + "clap", + "multiversx-sc", + "multiversx-sc-snippets", + "potlock", + "serde", + "toml", +] + +[[package]] +name = "potlock-meta" +version = "0.0.0" +dependencies = [ + "multiversx-sc-meta-lib", + "potlock", +] + [[package]] name = "ppv-lite86" version = "0.2.20" diff --git a/Cargo.toml b/Cargo.toml index 5fb5dd18..ec2024ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,9 @@ members = [ "contracts/paymaster/interactor", "contracts/ping-pong-egld", "contracts/ping-pong-egld/meta", + "contracts/potlock", + "contracts/potlock/meta", + "contracts/potlock/interact-rs", "contracts/proxy-deployer", "contracts/proxy-deployer/meta", "contracts/proxy-pause", diff --git a/contracts/potlock/Cargo.toml b/contracts/potlock/Cargo.toml new file mode 100644 index 00000000..281683a8 --- /dev/null +++ b/contracts/potlock/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "potlock" +version = "0.0.0" +authors = ["you"] +edition = "2021" +publish = false +readme = "README.md" + +[lib] +path = "src/potlock.rs" + +[dependencies.multiversx-sc] +version = "0.53.2" + +[dependencies.multiversx-sc-modules] +version = "=0.53.2" + +[dev-dependencies] +num-bigint = "0.4.2" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.53.2" diff --git a/contracts/potlock/README.md b/contracts/potlock/README.md new file mode 100644 index 00000000..f13a60fb --- /dev/null +++ b/contracts/potlock/README.md @@ -0,0 +1,27 @@ +# Potlock SC + +## Overview +Potlock is a smart contract designed to enhance and accelerate Public Goods Funding (PGF) by leveraging the power of blockchain technology. Inspired by the collaboration between Potlock and the NEAR Foundation, this contract aims to provide an efficient and transparent mechanism for funding public goods, encouraging community participation, and maximizing the impact of pooled resources. + +## Features +* **Public Goods Funding (PGF)**: Facilitates the pooling of resources for funding projects that benefit the public. +* **Transparent Allocation**: Ensures that funds are distributed transparently according to predefined rules. +* **Decentralized Governance:** Empowers the community to participate in decision-making processes related to fund allocation. +* **Efficient Fund Management**: Optimizes the management and distribution of funds to maximize impact. + + + +## How It Works + +2. **Activation**: +* A POT is defined, where backers and the foundation contribute tokens. +* Anyone can suggest a new POT by paying a fee, but the proposal must be accepted by the admin for it to be activated. +2. **Application and Review**: +* Projects submit their applications to the POT, aiming to secure funding. +* An authority reviews these submissions, evaluating their eligibility and potential impact. +3. **Donation and Matching**: +* Approved projects receive direct contributions from verified donors. +* These donations are further amplified by the POT's value through the Quadratic Funding (QF) model, maximizing the impact of smaller contributions. +4. **Payout**: +* Funds are distributed to the projects based on the matching model. +* After a cooldown period, the process can start again from step 1, with a new or existing POT. \ No newline at end of file diff --git a/contracts/potlock/interact-rs/.gitignore b/contracts/potlock/interact-rs/.gitignore new file mode 100644 index 00000000..5a64d09a --- /dev/null +++ b/contracts/potlock/interact-rs/.gitignore @@ -0,0 +1,2 @@ +# Pem files are used for interactions, but shouldn't be committed +*.pem diff --git a/contracts/potlock/interact-rs/Cargo.toml b/contracts/potlock/interact-rs/Cargo.toml new file mode 100644 index 00000000..4bf66a7b --- /dev/null +++ b/contracts/potlock/interact-rs/Cargo.toml @@ -0,0 +1,30 @@ +[[bin]] +name = "potlock-interact" +path = "src/potlock_interactor_main.rs" + +[package] +name = "potlock-interact" +version = "0.0.0" +authors = ["you"] +edition = "2021" +publish = false + +[dependencies] +toml = "0.8.6" + +[dependencies.potlock] +path = ".." + +[dependencies.multiversx-sc-snippets] +version = "0.53.2" + +[dependencies.multiversx-sc] +version = "0.53.2" + +[dependencies.clap] +version = "4.4.7" +features = ["derive"] + +[dependencies.serde] +version = "1.0" +features = ["derive"] diff --git a/contracts/potlock/interact-rs/config.toml b/contracts/potlock/interact-rs/config.toml new file mode 100644 index 00000000..dbbe25d0 --- /dev/null +++ b/contracts/potlock/interact-rs/config.toml @@ -0,0 +1,6 @@ +gateway = 'https://devnet-gateway.multiversx.com' +admin = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" +pot_proposer = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" +project_proposer = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" +pot_donor = "erd1kyaqzaprcdnv4luvanah0gfxzzsnpaygsy6pytrexll2urtd05ts9vegu7" +project_donor = "erd18tudnj2z8vjh0339yu3vrkgzz2jpz8mjq0uhgnmklnap6z33qqeszq2yn4" \ No newline at end of file diff --git a/contracts/potlock/interact-rs/src/potlock_interactor_config.rs b/contracts/potlock/interact-rs/src/potlock_interactor_config.rs new file mode 100644 index 00000000..77a34763 --- /dev/null +++ b/contracts/potlock/interact-rs/src/potlock_interactor_config.rs @@ -0,0 +1,32 @@ +use multiversx_sc_snippets::imports::Bech32Address; +use serde::Deserialize; +use std::io::Read; + +/// Config file +const CONFIG_FILE: &str = "config.toml"; + +/// Multisig Interact configuration +#[derive(Debug, Deserialize)] +pub struct Config { + gateway: String, + pub admin: Bech32Address, + pub pot_proposer: Bech32Address, + pub project_proposer: Bech32Address, + pub pot_donor: Bech32Address, + pub project_donor: Bech32Address, +} + +impl Config { + // Deserializes config from file + pub fn load_config() -> Self { + let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + toml::from_str(&content).unwrap() + } + + // Returns the gateway + pub fn gateway(&self) -> &str { + &self.gateway + } +} diff --git a/contracts/potlock/interact-rs/src/potlock_interactor_main.rs b/contracts/potlock/interact-rs/src/potlock_interactor_main.rs new file mode 100644 index 00000000..36f7b51a --- /dev/null +++ b/contracts/potlock/interact-rs/src/potlock_interactor_main.rs @@ -0,0 +1,1493 @@ +#![allow(non_snake_case)] +#![allow(unused_imports)] +#![allow(dead_code)] +#![allow(unused_variables)] + +mod proxy; + +use multiversx_sc_snippets::imports::*; +use multiversx_sc_snippets::sdk; +use serde::{Deserialize, Serialize}; +use std::char::MAX; +use std::result; +use std::{ + io::{Read, Write}, + path::Path, +}; + +const GATEWAY: &str = sdk::gateway::TESTNET_GATEWAY; +const STATE_FILE: &str = "state.toml"; +const TOKEN_ID: &str = "VLD-070dac"; +const SECOND_TOKEN_ID: &str = "SCND-620d29"; +const INVALID_TOKEN_ID: &str = "123"; +const FEE_AMOUNT: u128 = 1; +const DONATION_AMOUNT: u64 = 10; +const OWNER_ADDR: &str = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; +const SECOND_USER_ADDR: &str = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"; +const THIRD_USER_ADDR: &str = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"; +const MAX_PERCENTAGE: u64 = 10_000; +const BIG_ID: u32 = 1000u32; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let mut args = std::env::args(); + let _ = args.next(); + let cmd = args.next().expect("at least one argument required"); + let mut interact = ContractInteract::new().await; + match cmd.as_str() { + "deploy" => interact.deploy().await, + _ => panic!("unknown command: {}", &cmd), + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +struct State { + contract_address: Option, +} + +impl State { + // Deserializes state from file + pub fn load_state() -> Self { + if Path::new(STATE_FILE).exists() { + let mut file = std::fs::File::open(STATE_FILE).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + toml::from_str(&content).unwrap() + } else { + Self::default() + } + } + + /// Sets the contract address + pub fn set_address(&mut self, address: Bech32Address) { + self.contract_address = Some(address); + } + + /// Returns the contract address + pub fn current_address(&self) -> &Bech32Address { + self.contract_address + .as_ref() + .expect("no known contract, deploy first") + } +} + +impl Drop for State { + // Serializes state to file + fn drop(&mut self) { + let mut file = std::fs::File::create(STATE_FILE).unwrap(); + file.write_all(toml::to_string(self).unwrap().as_bytes()) + .unwrap(); + } +} + +struct ContractInteract { + interactor: Interactor, + owner_address: Address, + second_address: Address, + third_address: Address, + contract_code: BytesValue, + state: State, +} + +impl ContractInteract { + async fn new() -> Self { + let mut interactor = Interactor::new(GATEWAY).await; + let owner_address = interactor.register_wallet(test_wallets::alice()); + let second_address = interactor.register_wallet(test_wallets::bob()); + let third_address = interactor.register_wallet(test_wallets::carol()); + + let contract_code = BytesValue::interpret_from( + "mxsc:../output/potlock.mxsc.json", + &InterpreterContext::default(), + ); + + ContractInteract { + interactor, + owner_address, + second_address, + third_address, + contract_code, + state: State::load_state(), + } + } + + async fn deploy(&mut self) { + let admins = MultiValueVec::from(vec![bech32::decode(THIRD_USER_ADDR)]); + + let new_address = self + .interactor + .tx() + .from(&self.owner_address) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .init(admins) + .code(&self.contract_code) + .returns(ReturnsNewAddress) + .prepare_async() + .run() + .await; + let new_address_bech32 = bech32::encode(&new_address); + self.state.set_address(Bech32Address::from_bech32_string( + new_address_bech32.clone(), + )); + + println!("new address: {new_address_bech32}"); + } + + async fn upgrade(&mut self) { + let response = self + .interactor + .tx() + .to(self.state.current_address()) + .from(&self.owner_address) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .upgrade() + .code(&self.contract_code) + .code_metadata(CodeMetadata::UPGRADEABLE) + .returns(ReturnsNewAddress) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn change_fee_for_pots(&mut self, caller: &Bech32Address, token_id: &str, fee: u128) { + let token_identifier = TokenIdentifier::from_esdt_bytes(token_id); + let fee = BigUint::::from(fee); + + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .change_fee_for_pots(token_identifier, fee) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn change_fee_for_pots_fail( + &mut self, + caller: &Bech32Address, + token_id: &str, + fee: u128, + expected_result: ExpectError<'_>, + ) { + let token_identifier = TokenIdentifier::from_esdt_bytes(token_id); + let fee = BigUint::::from(fee); + + self.interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .change_fee_for_pots(token_identifier, fee) + .returns(expected_result) + .prepare_async() + .run() + .await; + } + + async fn accept_pot(&mut self, caller: &Bech32Address, potlock_id: u32) { + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .accept_pot(potlock_id) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn accept_pot_fail( + &mut self, + caller: &Bech32Address, + potlock_id: u32, + expected_result: ExpectError<'_>, + ) { + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .accept_pot(potlock_id) + .returns(expected_result) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn remove_pot(&mut self, caller: &Bech32Address, potlock_id: u32) { + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .remove_pot(potlock_id) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn remove_pot_fail( + &mut self, + caller: &Bech32Address, + potlock_id: u32, + expected_result: ExpectError<'_>, + ) { + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .remove_pot(potlock_id) + .returns(expected_result) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn accept_application(&mut self, caller: &Bech32Address, project_id: u32) { + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .accept_application(project_id) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn accept_application_fail( + &mut self, + caller: &Bech32Address, + project_id: u32, + expected_result: ExpectError<'_>, + ) { + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .accept_application(project_id) + .returns(expected_result) + .prepare_async() + .run() + .await; + } + + async fn remove_application(&mut self) { + let project_id = 0u32; + + let response = self + .interactor + .tx() + .from(&self.owner_address) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .remove_application(project_id) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn reject_donation(&mut self) { + let potlock_id = 0u32; + let user = bech32::decode(""); + + let response = self + .interactor + .tx() + .from(&self.owner_address) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .reject_donation(potlock_id, user) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn distribute_pot_to_projects( + &mut self, + caller: &Bech32Address, + potlock_id: u32, + project_percentages: MultiValueVec>, + ) { + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .distribute_pot_to_projects(potlock_id, project_percentages) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn distribute_pot_to_projects_fail( + &mut self, + caller: &Bech32Address, + potlock_id: u32, + project_percentages: MultiValueVec>, + expected_result: ExpectError<'_>, + ) { + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .distribute_pot_to_projects(potlock_id, project_percentages) + .returns(expected_result) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn add_pot(&mut self, caller: &Bech32Address, token_id: &str, fee: u128) { + let token_nonce = 0u64; + let token_amount = BigUint::::from(fee); + + let name = ManagedBuffer::new_from_bytes(&b""[..]); + let description = ManagedBuffer::new_from_bytes(&b""[..]); + + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .add_pot(name, description) + .payment((TokenIdentifier::from(token_id), token_nonce, token_amount)) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn add_pot_fail( + &mut self, + caller: &Bech32Address, + token_id: &str, + fee: u128, + expected_result: ExpectError<'_>, + ) { + let token_nonce = 0u64; + let token_amount = BigUint::::from(fee); + + let name = ManagedBuffer::new_from_bytes(&b""[..]); + let description = ManagedBuffer::new_from_bytes(&b""[..]); + + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .add_pot(name, description) + .payment((TokenIdentifier::from(token_id), token_nonce, token_amount)) + .returns(expected_result) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn apply_for_pot(&mut self, caller: &Bech32Address, potlock_id: u32) { + let project_name = ManagedBuffer::new_from_bytes(&b""[..]); + let description = ManagedBuffer::new_from_bytes(&b""[..]); + + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .apply_for_pot(potlock_id, project_name, description) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn donate_to_pot( + &mut self, + caller: &Bech32Address, + potlock_id: u32, + token_id: &str, + amount: u128, + ) { + let token_nonce = 0u64; + let token_amount = BigUint::::from(amount); + + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .donate_to_pot(potlock_id) + .payment((TokenIdentifier::from(token_id), token_nonce, token_amount)) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn donate_to_pot_fail( + &mut self, + caller: &Bech32Address, + potlock_id: u32, + token_id: &str, + amount: u128, + expected_result: ExpectError<'_>, + ) { + let token_nonce = 0u64; + let token_amount = BigUint::::from(amount); + + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .donate_to_pot(potlock_id) + .payment((TokenIdentifier::from(token_id), token_nonce, token_amount)) + .returns(expected_result) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn donate_to_project( + &mut self, + caller: &Bech32Address, + project_id: u32, + token_id: &str, + amount: u128, + ) { + let token_nonce = 0u64; + let token_amount = BigUint::::from(amount); + + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .donate_to_project(project_id) + .payment((TokenIdentifier::from(token_id), token_nonce, token_amount)) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn donate_to_project_fail( + &mut self, + caller: &Bech32Address, + project_id: u32, + token_id: &str, + amount: u128, + expected_result: ExpectError<'_>, + ) { + let token_nonce = 0u64; + let token_amount = BigUint::::from(amount); + + let response = self + .interactor + .tx() + .from(caller) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .donate_to_project(project_id) + .payment((TokenIdentifier::from(token_id), token_nonce, token_amount)) + .returns(expected_result) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn fee_token_identifier(&mut self) -> String { + let result_value = self + .interactor + .query() + .to(self.state.current_address()) + .typed(proxy::PotlockProxy) + .fee_token_identifier() + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + result_value.to_string() + } + + async fn fee_amount(&mut self) -> RustBigUint { + let result_value = self + .interactor + .query() + .to(self.state.current_address()) + .typed(proxy::PotlockProxy) + .fee_amount() + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {result_value:?}"); + + result_value + } + + async fn potlocks(&mut self) { + let result_value = self + .interactor + .query() + .to(self.state.current_address()) + .typed(proxy::PotlockProxy) + .potlocks() + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {result_value:?}"); + } + + async fn projects(&mut self) { + let result_value = self + .interactor + .query() + .to(self.state.current_address()) + .typed(proxy::PotlockProxy) + .projects() + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {result_value:?}"); + } + + async fn pot_donations(&mut self) { + let potlock_id = 0u32; + + let result_value = self + .interactor + .query() + .to(self.state.current_address()) + .typed(proxy::PotlockProxy) + .pot_donations(potlock_id) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {result_value:?}"); + } + + async fn project_donations(&mut self) { + let project_id = 0u32; + + let result_value = self + .interactor + .query() + .to(self.state.current_address()) + .typed(proxy::PotlockProxy) + .project_donations(project_id) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {result_value:?}"); + } + + async fn is_admin(&mut self) { + let address = bech32::decode(""); + + let result_value = self + .interactor + .query() + .to(self.state.current_address()) + .typed(proxy::PotlockProxy) + .is_admin(address) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {result_value:?}"); + } + + async fn add_admin(&mut self) { + let address = bech32::decode(""); + + let response = self + .interactor + .tx() + .from(&self.owner_address) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .add_admin(address) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn remove_admin(&mut self) { + let address = bech32::decode(""); + + let response = self + .interactor + .tx() + .from(&self.owner_address) + .to(self.state.current_address()) + .gas(70_000_000u64) + .typed(proxy::PotlockProxy) + .remove_admin(address) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + + async fn admins(&mut self) { + let result_value = self + .interactor + .query() + .to(self.state.current_address()) + .typed(proxy::PotlockProxy) + .admins() + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {result_value:?}"); + } +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_deploy_and_config() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_add_pot() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_accept_pot() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .accept_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_remove_pot() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .remove_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_donate_to_pot() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .accept_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .donate_to_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + TOKEN_ID, + DONATION_AMOUNT.into(), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_accept_application() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .accept_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .apply_for_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .accept_application( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_donate_to_project() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .accept_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .apply_for_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .accept_application( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .donate_to_project( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + TOKEN_ID, + DONATION_AMOUNT.into(), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_distribute_pot_to_projects() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .accept_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .donate_to_pot( + &Bech32Address::from_bech32_string(THIRD_USER_ADDR.to_string()), + 1u32, + TOKEN_ID, + DONATION_AMOUNT.into(), + ) + .await; + + interact + .apply_for_pot( + &Bech32Address::from_bech32_string(THIRD_USER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .accept_application( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + let project_percentages = MultiValueVec::from(vec![MultiValue2::from((1u32, MAX_PERCENTAGE))]); + + interact + .distribute_pot_to_projects( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + project_percentages, + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_donate_to_pot_twice_with_same_token() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .accept_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .donate_to_pot( + &Bech32Address::from_bech32_string(THIRD_USER_ADDR.to_string()), + 1u32, + TOKEN_ID, + DONATION_AMOUNT.into(), + ) + .await; + + interact + .donate_to_pot( + &Bech32Address::from_bech32_string(THIRD_USER_ADDR.to_string()), + 1u32, + TOKEN_ID, + (DONATION_AMOUNT + 1).into(), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_multiple_change_fee_for_pots() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + let fee = interact.fee_amount().await; + let token_id = interact.fee_token_identifier().await; + + assert_eq!(fee, FEE_AMOUNT.into()); + assert_eq!(token_id, TOKEN_ID.to_string()); + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + SECOND_TOKEN_ID, + FEE_AMOUNT + 1, + ) + .await; + + let fee = interact.fee_amount().await; + let token_id = interact.fee_token_identifier().await; + + assert_eq!(fee, FEE_AMOUNT.into()); + assert_eq!(token_id, TOKEN_ID.to_string()); +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_change_fee_for_pots_non_admin() { + let mut interact = ContractInteract::new().await; + + interact + .change_fee_for_pots_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + INVALID_TOKEN_ID, + FEE_AMOUNT, + ExpectError(4, "Endpoint can only be called by admins"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_accept_pot_non_admin() { + let mut interact = ContractInteract::new().await; + + interact + .accept_pot_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + ExpectError(4, "Endpoint can only be called by admins"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_remove_pot_non_admin() { + let mut interact = ContractInteract::new().await; + + interact + .remove_pot_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + ExpectError(4, "Endpoint can only be called by admins"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_accept_application_non_admin() { + let mut interact = ContractInteract::new().await; + + interact + .accept_application_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + ExpectError(4, "Endpoint can only be called by admins"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_distribute_pot_to_projects_non_admin() { + let mut interact = ContractInteract::new().await; + + let project_percentages = MultiValueVec::from(vec![MultiValue2::from((1u32, MAX_PERCENTAGE))]); + + interact + .distribute_pot_to_projects_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + project_percentages, + ExpectError(4, "Endpoint can only be called by admins"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_distribute_pot_to_projects_more_than_max_percent() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .accept_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .donate_to_pot( + &Bech32Address::from_bech32_string(THIRD_USER_ADDR.to_string()), + 1u32, + TOKEN_ID, + DONATION_AMOUNT.into(), + ) + .await; + + interact + .apply_for_pot( + &Bech32Address::from_bech32_string(THIRD_USER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .accept_application( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + let project_percentages = + MultiValueVec::from(vec![MultiValue2::from((1u32, MAX_PERCENTAGE + 1))]); + + interact + .distribute_pot_to_projects_fail( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + project_percentages, + ExpectError(4, "Total percentages more than 100%"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_donate_to_project_with_different_token() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .accept_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .apply_for_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .accept_application( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .donate_to_project( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + SECOND_TOKEN_ID, + DONATION_AMOUNT.into(), + ) + .await; + + interact + .donate_to_project_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + TOKEN_ID, + DONATION_AMOUNT.into(), + ExpectError(4, "Already made a payment with a different TokenID"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_donate_to_project_inactive_project() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .accept_pot( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .apply_for_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + ) + .await; + + interact + .donate_to_project_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + TOKEN_ID, + DONATION_AMOUNT.into(), + ExpectError(4, "Project is not active!"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_donate_to_pot_inactive_pot() { + let mut interact = ContractInteract::new().await; + + interact.deploy().await; + + interact + .change_fee_for_pots( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .add_pot( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT, + ) + .await; + + interact + .donate_to_pot_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + 1u32, + TOKEN_ID, + DONATION_AMOUNT.into(), + ExpectError(4, "Pot is not active!"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_add_pot_wrong_payment() { + let mut interact = ContractInteract::new().await; + + interact + .add_pot_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + SECOND_TOKEN_ID, + FEE_AMOUNT, + ExpectError(4, "Wrong token identifier for creating a pot!"), + ) + .await; + + interact + .add_pot_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + TOKEN_ID, + FEE_AMOUNT + 1, + ExpectError(4, "Wrong fee amount for creating a pot"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_accept_pot_non_existent() { + let mut interact = ContractInteract::new().await; + + interact + .accept_pot_fail( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + BIG_ID, + ExpectError(4, "Potlock doesn't exist!"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_remove_pot_non_existent() { + let mut interact = ContractInteract::new().await; + + interact + .remove_pot_fail( + &Bech32Address::from_bech32_string(OWNER_ADDR.to_string()), + BIG_ID, + ExpectError(4, "Potlock doesn't exist!"), + ) + .await; +} + +#[tokio::test] +#[ignore = "run on demand"] +async fn test_donate_to_pot_non_existent() { + let mut interact = ContractInteract::new().await; + + interact + .donate_to_pot_fail( + &Bech32Address::from_bech32_string(SECOND_USER_ADDR.to_string()), + BIG_ID, + TOKEN_ID, + DONATION_AMOUNT.into(), + ExpectError(4, "Potlock doesn't exist!"), + ) + .await; +} diff --git a/contracts/potlock/interact-rs/src/proxy.rs b/contracts/potlock/interact-rs/src/proxy.rs new file mode 100644 index 00000000..7abb2669 --- /dev/null +++ b/contracts/potlock/interact-rs/src/proxy.rs @@ -0,0 +1,390 @@ +// Code generated by the multiversx-sc proxy generator. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +#![allow(dead_code)] +#![allow(clippy::all)] + +use multiversx_sc::proxy_imports::*; + +pub struct PotlockProxy; + +impl TxProxyTrait for PotlockProxy +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + type TxProxyMethods = PotlockProxyMethods; + + fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { + PotlockProxyMethods { wrapped_tx: tx } + } +} + +pub struct PotlockProxyMethods +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + wrapped_tx: Tx, +} + +#[rustfmt::skip] +impl PotlockProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + Gas: TxGas, +{ + pub fn init< + Arg0: ProxyArg>>, + >( + self, + admins: Arg0, + ) -> TxTypedDeploy { + self.wrapped_tx + .payment(NotPayable) + .raw_deploy() + .argument(&admins) + .original_result() + } +} + +#[rustfmt::skip] +impl PotlockProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn upgrade( + self, + ) -> TxTypedUpgrade { + self.wrapped_tx + .payment(NotPayable) + .raw_upgrade() + .original_result() + } +} + +#[rustfmt::skip] +impl PotlockProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn change_fee_for_pots< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + token_identifier: Arg0, + fee: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("changeFeeForPots") + .argument(&token_identifier) + .argument(&fee) + .original_result() + } + + pub fn accept_pot< + Arg0: ProxyArg, + >( + self, + potlock_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("acceptPot") + .argument(&potlock_id) + .original_result() + } + + pub fn remove_pot< + Arg0: ProxyArg, + >( + self, + potlock_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removePot") + .argument(&potlock_id) + .original_result() + } + + pub fn accept_application< + Arg0: ProxyArg, + >( + self, + project_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("acceptApplication") + .argument(&project_id) + .original_result() + } + + pub fn remove_application< + Arg0: ProxyArg, + >( + self, + project_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeApplication") + .argument(&project_id) + .original_result() + } + + pub fn reject_donation< + Arg0: ProxyArg, + Arg1: ProxyArg>, + >( + self, + potlock_id: Arg0, + user: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("rejectDonation") + .argument(&potlock_id) + .argument(&user) + .original_result() + } + + pub fn distribute_pot_to_projects< + Arg0: ProxyArg, + Arg1: ProxyArg>>, + >( + self, + potlock_id: Arg0, + project_percentages: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("distributePotToProjects") + .argument(&potlock_id) + .argument(&project_percentages) + .original_result() + } + + pub fn add_pot< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + name: Arg0, + description: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("addPot") + .argument(&name) + .argument(&description) + .original_result() + } + + pub fn apply_for_pot< + Arg0: ProxyArg, + Arg1: ProxyArg>, + Arg2: ProxyArg>, + >( + self, + potlock_id: Arg0, + project_name: Arg1, + description: Arg2, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("applyForPot") + .argument(&potlock_id) + .argument(&project_name) + .argument(&description) + .original_result() + } + + pub fn donate_to_pot< + Arg0: ProxyArg, + >( + self, + potlock_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("donateToPot") + .argument(&potlock_id) + .original_result() + } + + pub fn donate_to_project< + Arg0: ProxyArg, + >( + self, + project_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("donateToProject") + .argument(&project_id) + .original_result() + } + + pub fn fee_token_identifier( + self, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getFeeTokenIdentifier") + .original_result() + } + + pub fn fee_amount( + self, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getFeeAmount") + .original_result() + } + + pub fn potlocks( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getPotlocks") + .original_result() + } + + pub fn projects( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getProjects") + .original_result() + } + + pub fn pot_donations< + Arg0: ProxyArg, + >( + self, + potlock_id: Arg0, + ) -> TxTypedCall, EsdtTokenPayment>>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("potDonations") + .argument(&potlock_id) + .original_result() + } + + pub fn project_donations< + Arg0: ProxyArg, + >( + self, + project_id: Arg0, + ) -> TxTypedCall, EsdtTokenPayment>>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("projectDonations") + .argument(&project_id) + .original_result() + } + + pub fn is_admin< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("isAdmin") + .argument(&address) + .original_result() + } + + pub fn add_admin< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addAdmin") + .argument(&address) + .original_result() + } + + pub fn remove_admin< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeAdmin") + .argument(&address) + .original_result() + } + + pub fn admins( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getAdmins") + .original_result() + } +} + +#[type_abi] +#[derive(TopEncode, TopDecode, Debug)] +pub struct Pot +where + Api: ManagedTypeApi, +{ + pub potlock_id: usize, + pub proposer: ManagedAddress, + pub token_identifier: TokenIdentifier, + pub fee: BigUint, + pub name: ManagedBuffer, + pub description: ManagedBuffer, + pub status: Status, +} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedDecode, NestedEncode, Debug)] +pub enum Status { + Inactive, + Active, +} + +#[type_abi] +#[derive(TopEncode, TopDecode, Debug)] +pub struct Project +where + Api: ManagedTypeApi, +{ + pub potlock_id: usize, + pub name: ManagedBuffer, + pub description: ManagedBuffer, + pub owner: ManagedAddress, + pub status: Status, +} diff --git a/contracts/potlock/interact-rs/state.toml b/contracts/potlock/interact-rs/state.toml new file mode 100644 index 00000000..6a70e4cf --- /dev/null +++ b/contracts/potlock/interact-rs/state.toml @@ -0,0 +1 @@ +contract_address = "erd1qqqqqqqqqqqqqpgq5upecnmqrjd673jhuy36z7ehdgsgkkuvd8ssdxdcaq" diff --git a/contracts/potlock/meta/Cargo.toml b/contracts/potlock/meta/Cargo.toml new file mode 100644 index 00000000..af05e594 --- /dev/null +++ b/contracts/potlock/meta/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "potlock-meta" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies.potlock] +path = ".." + +[dependencies.multiversx-sc-meta-lib] +version = "0.53.2" +default-features = false diff --git a/contracts/potlock/meta/src/main.rs b/contracts/potlock/meta/src/main.rs new file mode 100644 index 00000000..1514f0fc --- /dev/null +++ b/contracts/potlock/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta_lib::cli_main::(); +} diff --git a/contracts/potlock/multiversx.json b/contracts/potlock/multiversx.json new file mode 100644 index 00000000..73655396 --- /dev/null +++ b/contracts/potlock/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/contracts/potlock/sc-config.toml b/contracts/potlock/sc-config.toml new file mode 100644 index 00000000..15d4cf88 --- /dev/null +++ b/contracts/potlock/sc-config.toml @@ -0,0 +1,4 @@ + +[[proxy]] +path = "interact-rs/src/proxy.rs" + diff --git a/contracts/potlock/src/potlock.rs b/contracts/potlock/src/potlock.rs new file mode 100644 index 00000000..6d0502d4 --- /dev/null +++ b/contracts/potlock/src/potlock.rs @@ -0,0 +1,29 @@ +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); +pub mod potlock_admin_interactions; +pub mod potlock_interactions; +pub mod potlock_requirements; +pub mod potlock_storage; + +#[multiversx_sc::contract] +pub trait Potlock: + potlock_admin_interactions::PotlockAdminInteractions + + potlock_interactions::PotlockInteractions + + potlock_requirements::PotlockRequirements + + potlock_storage::PotlockStorage + + multiversx_sc_modules::only_admin::OnlyAdminModule +{ + #[init] + fn init(&self, admins: MultiValueEncoded) { + let caller = self.blockchain().get_caller(); + self.admins().insert(caller); + for admin in admins { + self.admins().insert(admin); + } + } + + #[upgrade] + fn upgrade(&self) {} +} diff --git a/contracts/potlock/src/potlock_admin_interactions.rs b/contracts/potlock/src/potlock_admin_interactions.rs new file mode 100644 index 00000000..bf38a939 --- /dev/null +++ b/contracts/potlock/src/potlock_admin_interactions.rs @@ -0,0 +1,132 @@ +use crate::{ + potlock_requirements::{self, MAX_PERCENTAGE}, + potlock_storage::{self, PotlockId, ProjectId, Status}, +}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub type ProjectPercentage = MultiValue2; + +#[multiversx_sc::module] +pub trait PotlockAdminInteractions: + potlock_requirements::PotlockRequirements + + potlock_storage::PotlockStorage + + multiversx_sc_modules::only_admin::OnlyAdminModule +{ + #[only_admin] + #[endpoint(changeFeeForPots)] + fn change_fee_for_pots(&self, token_identifier: TokenIdentifier, fee: BigUint) { + require!( + token_identifier.is_valid_esdt_identifier(), + "Invalid token provided" + ); + require!(fee > 0, "Amount is 0"); + self.fee_token_identifier().set_if_empty(&token_identifier); + self.fee_amount().set_if_empty(fee); + } + + #[only_admin] + #[endpoint(acceptPot)] + fn accept_pot(&self, potlock_id: PotlockId) { + self.require_potlock_exists(potlock_id); + self.require_potlock_is_inactive(potlock_id); + + let mut accepted_potlock = self.potlocks().get(potlock_id); + accepted_potlock.status = Status::Active; + self.potlocks().set(potlock_id, &accepted_potlock); + } + + #[only_admin] + #[endpoint(removePot)] + fn remove_pot(&self, potlock_id: PotlockId) { + self.require_potlock_exists(potlock_id); + self.require_potlock_is_inactive(potlock_id); + + let potlock_mapper = self.potlocks(); + let pot_proposer = potlock_mapper.get(potlock_id).proposer; + let fee_pot_payment = EsdtTokenPayment::new( + self.fee_token_identifier().get(), + 0u64, + self.fee_amount().get(), + ); + + self.send() + .direct_non_zero_esdt_payment(&pot_proposer, &fee_pot_payment); + self.potlocks().clear_entry(potlock_id); + } + + #[only_admin] + #[endpoint(acceptApplication)] + fn accept_application(&self, project_id: ProjectId) { + self.require_project_exists(project_id); + self.require_project_is_inactive(project_id); + + let mut accepted_project = self.projects().get(project_id); + accepted_project.status = Status::Active; + self.projects().set(project_id, &accepted_project); + } + + #[only_admin] + #[endpoint(removeApplication)] + fn remove_application(&self, project_id: ProjectId) { + self.require_project_exists(project_id); + self.require_project_is_active(project_id); + + let mut rejected_project = self.projects().get(project_id); + rejected_project.status = Status::Inactive; + self.projects().set(project_id, &rejected_project); + } + + #[only_admin] + #[endpoint(rejectDonation)] + fn reject_donation(&self, potlock_id: PotlockId, user: ManagedAddress) { + self.require_potlock_exists(potlock_id); + let opt_fee_pot_payment = self.pot_donations(potlock_id).get(&user); + + require!(opt_fee_pot_payment.is_some(), "No donation for this user"); + let fee_pot_payment = unsafe { opt_fee_pot_payment.unwrap_unchecked() }; + + self.send() + .direct_non_zero_esdt_payment(&user, &fee_pot_payment); + self.pot_donations(potlock_id).remove(&user); + } + + #[only_admin] + #[endpoint(distributePotToProjects)] + fn distribute_pot_to_projects( + &self, + potlock_id: PotlockId, + project_percentages: MultiValueEncoded, + ) { + self.require_potlock_exists(potlock_id); + self.require_correct_percentages(project_percentages.clone()); + let pot_donations = self.pot_donations(potlock_id); + let all_projects = self.projects(); + + for pp in project_percentages { + let (project_id, percentage) = pp.into_tuple(); + let project = all_projects.get(project_id); + + // The project must previously apply to this Pot + if project.potlock_id != potlock_id { + continue; + } + + let mut output_payments = ManagedVec::new(); + for (_, donation) in pot_donations.iter() { + let project_share_amount = donation.amount * percentage / MAX_PERCENTAGE; + let project_share = EsdtTokenPayment::new( + donation.token_identifier, + donation.token_nonce, + project_share_amount, + ); + output_payments.push(project_share); + } + let project_owner = self.projects().get(project_id).owner; + self.send().direct_multi(&project_owner, &output_payments); + } + + self.pot_donations(potlock_id).clear(); + } +} diff --git a/contracts/potlock/src/potlock_interactions.rs b/contracts/potlock/src/potlock_interactions.rs new file mode 100644 index 00000000..15ebbd09 --- /dev/null +++ b/contracts/potlock/src/potlock_interactions.rs @@ -0,0 +1,106 @@ +use crate::potlock_requirements; +use crate::potlock_storage::{self, Pot, Project}; +use crate::potlock_storage::{PotlockId, ProjectId}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait PotlockInteractions: + potlock_requirements::PotlockRequirements + + potlock_storage::PotlockStorage + + multiversx_sc_modules::only_admin::OnlyAdminModule +{ + #[payable("*")] + #[endpoint(addPot)] + fn add_pot(&self, name: ManagedBuffer, description: ManagedBuffer) { + let payment_for_adding_pot = self.call_value().single_esdt(); + require!( + self.fee_token_identifier().get() == payment_for_adding_pot.token_identifier, + "Wrong token identifier for creating a pot!" + ); + require!( + self.fee_amount().get() == payment_for_adding_pot.amount, + "Wrong fee amount for creating a pot" + ); + let caller = self.blockchain().get_caller(); + + let potlock_id = self.potlocks().len() + 1; + let potlock = Pot::new(potlock_id, caller, name, description); + self.potlocks().push(&potlock); + } + + #[endpoint(applyForPot)] + fn apply_for_pot( + &self, + potlock_id: PotlockId, + project_name: ManagedBuffer, + description: ManagedBuffer, + ) -> usize { + let owner = self.blockchain().get_caller(); + let project = Project::new(potlock_id, project_name, description, owner); + self.projects().push(&project) + } + + #[payable("*")] + #[endpoint(donateToPot)] + fn donate_to_pot(&self, potlock_id: PotlockId) { + self.require_potlock_exists(potlock_id); + self.require_potlock_is_active(potlock_id); + + let payment = self.call_value().single_esdt(); + let caller = self.blockchain().get_caller(); + let mut donation_mapper = self.pot_donations(potlock_id); + + if donation_mapper.contains_key(&caller) { + let opt_payment = donation_mapper.get(&caller); + if opt_payment.is_some() { + let mut previous_payment = opt_payment.unwrap(); + require!( + previous_payment.token_identifier == payment.token_identifier.clone(), + "Already made a payment with a different TokenID" + ); + previous_payment.amount += payment.amount; + donation_mapper.insert(caller, previous_payment); + } + } else { + donation_mapper.insert(caller, payment); + } + + // match donation_mapper.get(&caller) { + // Some(mut previous_payment) => { + // // let a = pot_donations.get(&caller).unwrap(); + // previous_payment.amount += payment.amount; + // pot_donations.insert(caller, previous_payment); + // } + // None => { + // self.pot_donations(potlock_id).insert(caller, payment); + // } + // } + } + + #[payable("*")] + #[endpoint(donateToProject)] + fn donate_to_project(&self, project_id: ProjectId) { + self.require_project_exists(project_id); + self.require_project_is_active(project_id); + let payment = self.call_value().single_esdt(); + let caller = self.blockchain().get_caller(); + + let mut donation_mapper = self.project_donations(project_id); + if donation_mapper.contains_key(&caller) { + let opt_payment = donation_mapper.get(&caller); + if opt_payment.is_some() { + let mut previous_payment = opt_payment.unwrap(); + require!( + previous_payment.token_identifier == payment.token_identifier.clone(), + "Already made a payment with a different TokenID" + ); + previous_payment.amount += payment.amount; + donation_mapper.insert(caller, previous_payment); + } + } else { + donation_mapper.insert(caller, payment); + } + } +} diff --git a/contracts/potlock/src/potlock_requirements.rs b/contracts/potlock/src/potlock_requirements.rs new file mode 100644 index 00000000..f923672a --- /dev/null +++ b/contracts/potlock/src/potlock_requirements.rs @@ -0,0 +1,69 @@ +use crate::{ + potlock_admin_interactions::ProjectPercentage, + potlock_storage::{self, PotlockId, ProjectId, Status}, +}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub const MAX_PERCENTAGE: u64 = 10_000; // 100% + +#[multiversx_sc::module] +pub trait PotlockRequirements: potlock_storage::PotlockStorage { + fn is_valid_potlock_id(&self, potlock_id: PotlockId) -> bool { + potlock_id >= 1 && potlock_id <= self.potlocks().len() + } + + fn require_potlock_exists(&self, potlock_id: PotlockId) { + require!( + self.is_valid_potlock_id(potlock_id) && !self.potlocks().item_is_empty(potlock_id), + "Potlock doesn't exist!", + ) + } + + fn require_potlock_is_active(&self, potlock_id: PotlockId) { + let potlock = self.potlocks().get(potlock_id); + require!(potlock.status == Status::Active, "Pot is not active!",) + } + + fn require_potlock_is_inactive(&self, potlock_id: PotlockId) { + let potlock = self.potlocks().get(potlock_id); + require!(potlock.status != Status::Active, "Pot is active!",) + } + + fn is_valid_project_id(&self, project_id: ProjectId) -> bool { + project_id >= 1 && project_id <= self.projects().len() + } + + fn require_project_exists(&self, project_id: ProjectId) { + require!( + self.is_valid_project_id(project_id) && !self.projects().item_is_empty(project_id), + "Project doesn't exist!", + ) + } + + fn require_project_is_active(&self, project_id: ProjectId) { + let project = self.projects().get(project_id); + require!(project.status == Status::Active, "Project is not active!",) + } + + fn require_project_is_inactive(&self, project_id: ProjectId) { + let project = self.projects().get(project_id); + require!(project.status != Status::Active, "Project is active!",) + } + + fn require_correct_percentages( + &self, + project_percentages: MultiValueEncoded, + ) { + let mut total_perc: u64 = 0; + for pp in project_percentages { + let (_, perc) = pp.into_tuple(); + total_perc += perc; + } + require!( + total_perc <= MAX_PERCENTAGE, + "Total percentages more than 100%" + ); + } +} diff --git a/contracts/potlock/src/potlock_storage.rs b/contracts/potlock/src/potlock_storage.rs new file mode 100644 index 00000000..19b71bdf --- /dev/null +++ b/contracts/potlock/src/potlock_storage.rs @@ -0,0 +1,103 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub type PotlockId = usize; +pub type ProjectId = usize; + +#[derive(TypeAbi, TopEncode, TopDecode, PartialEq, Eq, Debug, NestedEncode, NestedDecode)] +pub enum Status { + Inactive, + Active, +} + +#[derive(TypeAbi, NestedEncode, NestedDecode, PartialEq, Debug, TopEncode, TopDecode)] +pub struct Pot { + pub potlock_id: PotlockId, + pub proposer: ManagedAddress, + pub token_identifier: TokenIdentifier, + pub fee: BigUint, + pub name: ManagedBuffer, + pub description: ManagedBuffer, + pub status: Status, +} + +impl Pot { + pub fn new( + potlock_id: PotlockId, + proposer: ManagedAddress, + name: ManagedBuffer, + description: ManagedBuffer, + ) -> Self { + Pot { + potlock_id, + proposer, + token_identifier: TokenIdentifier::from(ManagedBuffer::default()), + fee: BigUint::default(), + name, + description, + status: Status::Inactive, + } + } +} + +#[derive(TypeAbi, NestedEncode, NestedDecode, PartialEq, Debug, TopEncode, TopDecode)] +pub struct Project { + pub potlock_id: PotlockId, + pub name: ManagedBuffer, + pub description: ManagedBuffer, + pub owner: ManagedAddress, + pub status: Status, +} + +impl Project { + pub fn new( + potlock_id: PotlockId, + name: ManagedBuffer, + description: ManagedBuffer, + owner: ManagedAddress, + ) -> Self { + Project { + potlock_id, + name, + description, + owner, + status: Status::Inactive, + } + } +} + +#[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode, PartialEq, Debug)] +pub struct UserDonations { + pub user: ManagedAddress, + pub donations: EsdtTokenPayment, +} + +#[multiversx_sc::module] +pub trait PotlockStorage { + #[view(getFeeTokenIdentifier)] + #[storage_mapper("feeTokenIdentifier")] + fn fee_token_identifier(&self) -> SingleValueMapper; + + #[view(getFeeAmount)] + #[storage_mapper("feeAmount")] + fn fee_amount(&self) -> SingleValueMapper; + + #[view(getPotlocks)] + #[storage_mapper("potlocks")] + fn potlocks(&self) -> VecMapper>; + + #[view(getProjects)] + #[storage_mapper("projects")] + fn projects(&self) -> VecMapper>; + + #[view(potDonations)] + #[storage_mapper("potDonations")] + fn pot_donations(&self, potlock_id: PotlockId) -> MapMapper; + + #[view(projectDonations)] + #[storage_mapper("projectDonations")] + fn project_donations( + &self, + project_id: ProjectId, + ) -> MapMapper; +} diff --git a/contracts/potlock/tests/potlock_blackbox_tests.rs b/contracts/potlock/tests/potlock_blackbox_tests.rs new file mode 100644 index 00000000..725e625b --- /dev/null +++ b/contracts/potlock/tests/potlock_blackbox_tests.rs @@ -0,0 +1,729 @@ +use multiversx_sc_scenario::{imports::*, ScenarioWorld}; +use potlock::potlock_storage::{PotlockId, ProjectId}; +mod potlock_proxy; + +const POTLOCK_ADDRESS: TestSCAddress = TestSCAddress::new("potlock"); +const POTLOCK_CODE_PATH: MxscPath = MxscPath::new("output/potlock.mxsc.json"); +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const ADMIN_ADDRESS: TestAddress = TestAddress::new("admin"); +const POT_PROPOSER_ADDRESS: TestAddress = TestAddress::new("pot_proposer"); +const PROJECT_PROPOSER_ADDRESS: TestAddress = TestAddress::new("project_proposer"); +const POT_DONOR_ADDRESS: TestAddress = TestAddress::new("pot_donor"); +const PROJECT_DONOR_ADDRESS: TestAddress = TestAddress::new("project_donor"); +const POT_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("POT-123456"); +const DIFFERENT_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("DIFFPOT-123456"); +const POT_FEE_CREATION: u64 = 1_000; +const INITIAL_BALANCE: u64 = 2_000; +const DONATION_AMOUNT: u64 = 100; +const HALF_PERCENTAGE: u64 = 5_000; // 50% +const MAX_PERCENTAGE: u64 = 10_000; // 100% + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + + blockchain.register_contract(POTLOCK_CODE_PATH, potlock::ContractBuilder); + blockchain +} + +struct PotlockTestState { + world: ScenarioWorld, +} + +impl PotlockTestState { + fn new() -> Self { + let mut world = world(); + + world + .account(OWNER_ADDRESS) + .nonce(1) + .account(ADMIN_ADDRESS) + .nonce(1) + .account(POT_PROPOSER_ADDRESS) + .nonce(1) + .esdt_balance(POT_TOKEN_ID, INITIAL_BALANCE) + .account(PROJECT_PROPOSER_ADDRESS) + .nonce(1) + .account(POT_DONOR_ADDRESS) + .nonce(1) + .esdt_balance(POT_TOKEN_ID, INITIAL_BALANCE) + .esdt_balance(DIFFERENT_TOKEN_ID, INITIAL_BALANCE) + .account(PROJECT_DONOR_ADDRESS) + .nonce(1) + .esdt_balance(POT_TOKEN_ID, INITIAL_BALANCE) + .esdt_balance(DIFFERENT_TOKEN_ID, INITIAL_BALANCE); + + Self { world } + } + + fn deploy_potlock_contract(&mut self) -> &mut Self { + let mut admins: MultiValueEncoded> = + MultiValueEncoded::new(); + admins.push(ADMIN_ADDRESS.to_managed_address()); + + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .init(admins) + .code(POTLOCK_CODE_PATH) + .new_address(POTLOCK_ADDRESS) + .run(); + self + } + + fn change_fee_for_pots(&mut self, fee_amount: u64) { + self.world + .tx() + .from(ADMIN_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .change_fee_for_pots( + TokenIdentifier::from(POT_TOKEN_ID), + BigUint::from(fee_amount), + ) + .run(); + } + + fn add_pot(&mut self, name: &str, description: &str) { + self.world + .tx() + .from(POT_PROPOSER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .add_pot(name, description) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(POT_TOKEN_ID), + 0u64, + &multiversx_sc::proxy_imports::BigUint::from(POT_FEE_CREATION), + ) + .run(); + } + + fn accept_pot(&mut self, potlock_id: PotlockId) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .accept_pot(potlock_id) + .run(); + } + + fn accept_application(&mut self, project_id: ProjectId) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .accept_application(project_id) + .run(); + } + + fn remove_pot(&mut self, potlock_id: PotlockId) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .remove_pot(potlock_id) + .run(); + } + + fn apply_for_pot( + &mut self, + potlock_id: PotlockId, + project_name: &str, + description: &str, + ) -> usize { + let new_project_id = self + .world + .tx() + .from(PROJECT_PROPOSER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .apply_for_pot(potlock_id, project_name, description) + .returns(ReturnsResult) + .run(); + + new_project_id + } + + fn donate_to_pot(&mut self, potlock_id: PotlockId, donation_token: TestTokenIdentifier) { + self.world + .tx() + .from(POT_DONOR_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .donate_to_pot(potlock_id) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(donation_token), + 0u64, + &multiversx_sc::proxy_imports::BigUint::from(DONATION_AMOUNT), + ) + .run(); + } + + fn donate_to_project(&mut self, project_id: ProjectId) { + self.world + .tx() + .from(PROJECT_DONOR_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .donate_to_project(project_id) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(POT_TOKEN_ID), + 0u64, + &multiversx_sc::proxy_imports::BigUint::from(DONATION_AMOUNT), + ) + .run(); + } + + fn distribute_pot_to_projects( + &mut self, + potlock_id: PotlockId, + percentages: MultiValueVec>, + ) { + self.world + .tx() + .from(ADMIN_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .distribute_pot_to_projects(potlock_id, percentages) + .run(); + } + + ////////// Checks ////////// + fn check_esdt_balance(&mut self, address: TestAddress, balance: u64) { + self.world + .check_account(address) + .esdt_balance(POT_TOKEN_ID, balance); + } + + fn check_sc_esdt_balance(&mut self, address: TestSCAddress, balance: u64) { + self.world + .check_account(address) + .esdt_balance(POT_TOKEN_ID, balance); + } + + fn check_potlock_id_is_last(&mut self, potlock_id: PotlockId) { + let potlocks = self + .world + .query() + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .potlocks() + .returns(ReturnsResult) + .run(); + assert_eq!(potlocks.len(), potlock_id); + } + + fn check_project_id_is_last(&mut self, project_id: PotlockId) { + let projects = self + .world + .query() + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .projects() + .returns(ReturnsResult) + .run(); + assert_eq!(projects.len(), project_id); + } + + fn check_project_is_accepted(&mut self, project_description: ManagedBuffer) { + let projects = self + .world + .query() + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .projects() + .returns(ReturnsResult) + .run(); + + let mut project_found = false; + for project in projects.into_iter() { + if project.description == project_description { + project_found = true; + } + } + assert!(project_found); + } +} + +#[test] +fn test_deploy_and_config() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); +} + +#[test] +fn test_add_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + + let potlock_id = 1usize; + state.check_potlock_id_is_last(potlock_id); + + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); +} + +#[test] +fn test_accept_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + + let potlock_id = 1usize; + state.check_potlock_id_is_last(potlock_id); + + state.accept_pot(potlock_id); + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); +} + +#[test] +fn test_remove_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id = 1usize; + + state.remove_pot(potlock_id); + + // Funds were returned to user + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, 0); +} + +#[test] +fn test_apply_for_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id = 1usize; + + let new_project_id = state.apply_for_pot(potlock_id, "Project name", "Project description"); + + state.check_potlock_id_is_last(potlock_id); + state.check_project_id_is_last(new_project_id); + + // Funds were returned to user + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); +} + +#[test] +fn test_donate_to_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id = 1usize; + state.check_potlock_id_is_last(potlock_id); + + // Accept Pot + state.accept_pot(potlock_id); + + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); + state.check_esdt_balance(POT_DONOR_ADDRESS, INITIAL_BALANCE); + + state.donate_to_pot(potlock_id, POT_TOKEN_ID); + + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION + DONATION_AMOUNT); + state.check_esdt_balance(POT_DONOR_ADDRESS, INITIAL_BALANCE - DONATION_AMOUNT); +} + +#[test] +fn test_donate_to_project() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id = 1usize; + state.check_potlock_id_is_last(potlock_id); + + // Accept Pot + state.accept_pot(potlock_id); + + let new_project_id = state.apply_for_pot(potlock_id, "Project name", "Project description"); + state.check_project_id_is_last(new_project_id); + + state.accept_application(new_project_id); + state.check_project_is_accepted(ManagedBuffer::from("Project description")); + + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); + state.check_esdt_balance(PROJECT_DONOR_ADDRESS, INITIAL_BALANCE); + + state.donate_to_project(new_project_id); + + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION + DONATION_AMOUNT); + state.check_esdt_balance(PROJECT_DONOR_ADDRESS, INITIAL_BALANCE - DONATION_AMOUNT); +} + +#[test] +fn test_accept_application() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id: usize = 1usize; + state.check_potlock_id_is_last(potlock_id); + + let new_project_id = state.apply_for_pot(potlock_id, "Project name", "Project description"); + + state.check_project_id_is_last(new_project_id); + state.check_esdt_balance(POT_PROPOSER_ADDRESS, POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); + + state.accept_application(new_project_id); + state.check_project_is_accepted(ManagedBuffer::from("Project description")); +} + +#[test] +fn test_distribute_pot_to_projects() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + // Add Pot + state.add_pot("Pot", "Pot Description"); + let potlock_id: usize = 1usize; + state.check_potlock_id_is_last(potlock_id); + + // Accept Pot + state.accept_pot(potlock_id); + + // Add Project + let new_project_id = state.apply_for_pot(potlock_id, "Project name", "Project description"); + + state.check_project_id_is_last(new_project_id); + state.check_esdt_balance(POT_PROPOSER_ADDRESS, POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); + + // Donate to Pot + state.donate_to_pot(potlock_id, POT_TOKEN_ID); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION + DONATION_AMOUNT); + state.check_esdt_balance(POT_DONOR_ADDRESS, INITIAL_BALANCE - DONATION_AMOUNT); + + // Accept project + state.accept_application(new_project_id); + state.check_project_is_accepted(ManagedBuffer::from("Project description")); + + // Distribute Pot donations to projects + let mut percentages = MultiValueVec::new(); + percentages.push((new_project_id, MAX_PERCENTAGE).into()); + state.distribute_pot_to_projects(potlock_id, percentages); + + state.check_esdt_balance(PROJECT_PROPOSER_ADDRESS, DONATION_AMOUNT); +} + +///////////// Negative tests ////////////// + +#[test] +fn test_fail_add_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state + .world + .tx() + .from(POT_PROPOSER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .add_pot("name", "description") + .with_result(ExpectError(4, "incorrect number of ESDT transfers")) + .run(); + + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, 0); +} + +#[test] +fn test_fail_accept_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + + let potlock_id = 1usize; + state + .world + .tx() + .from(POT_PROPOSER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .accept_pot(potlock_id) + .with_result(ExpectError(4, "Endpoint can only be called by admins")) + .run(); + + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); +} + +#[test] +fn test_fail_remove_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + + let potlock_id = 1usize; + state + .world + .tx() + .from(POT_PROPOSER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .remove_pot(potlock_id) + .with_result(ExpectError(4, "Endpoint can only be called by admins")) + .run(); + + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); +} + +#[test] +fn test_fail_accept_application() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id = 1usize; + + let new_project_id = state.apply_for_pot(potlock_id, "Project name", "Project description"); + + state.check_potlock_id_is_last(potlock_id); + state.check_project_id_is_last(new_project_id); + + // Funds were returned to user + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); + + state + .world + .tx() + .from(POT_PROPOSER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .accept_application(new_project_id) + .with_result(ExpectError(4, "Endpoint can only be called by admins")) + .run(); +} + +#[test] +fn test_fail_distribute_pot_to_projects() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id: usize = 1usize; + state.check_potlock_id_is_last(potlock_id); + + let new_project_id = state.apply_for_pot(potlock_id, "Project name", "Project description"); + + state.check_project_id_is_last(new_project_id); + state.check_esdt_balance(POT_PROPOSER_ADDRESS, POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); + + state.accept_application(new_project_id); + state.check_project_is_accepted(ManagedBuffer::from("Project description")); + + let mut percentages = MultiValueVec::new(); + percentages.push((new_project_id, HALF_PERCENTAGE).into()); + state + .world + .tx() + .from(POT_PROPOSER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .distribute_pot_to_projects(potlock_id, percentages) + .with_result(ExpectError(4, "Endpoint can only be called by admins")) + .run(); +} + +#[test] +fn test_fail_distribute_pot_to_projects2() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + // Add Pot + state.add_pot("Pot", "Pot Description"); + let potlock_id: usize = 1usize; + state.check_potlock_id_is_last(potlock_id); + + // Accept Pot + state.accept_pot(potlock_id); + + // Add Project + let new_project_id = state.apply_for_pot(potlock_id, "Project name", "Project description"); + + state.check_project_id_is_last(new_project_id); + state.check_esdt_balance(POT_PROPOSER_ADDRESS, POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); + + // Donate to Pot + state.donate_to_pot(potlock_id, POT_TOKEN_ID); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION + DONATION_AMOUNT); + state.check_esdt_balance(POT_DONOR_ADDRESS, INITIAL_BALANCE - DONATION_AMOUNT); + + // Accept project + state.accept_application(new_project_id); + state.check_project_is_accepted(ManagedBuffer::from("Project description")); + + // Distribute Pot donations to projects + let mut percentages = MultiValueVec::new(); + percentages.push((new_project_id, 3 * HALF_PERCENTAGE).into()); + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .distribute_pot_to_projects(potlock_id, percentages) + .with_result(ExpectError(4, "Total percentages more than 100%")) + .run(); +} + +#[test] +fn test_fail_donate_to_project() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id = 1usize; + state.check_potlock_id_is_last(potlock_id); + + // Accept Pot + state.accept_pot(potlock_id); + + let new_project_id = state.apply_for_pot(potlock_id, "Project name", "Project description"); + state.check_project_id_is_last(new_project_id); + + state.accept_application(new_project_id); + state.check_project_is_accepted(ManagedBuffer::from("Project description")); + + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); + state.check_esdt_balance(PROJECT_DONOR_ADDRESS, INITIAL_BALANCE); + + state.donate_to_project(new_project_id); + + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION + DONATION_AMOUNT); + state.check_esdt_balance(PROJECT_DONOR_ADDRESS, INITIAL_BALANCE - DONATION_AMOUNT); + + state + .world + .tx() + .from(PROJECT_DONOR_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .donate_to_project(new_project_id) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(DIFFERENT_TOKEN_ID), + 0u64, + &multiversx_sc::proxy_imports::BigUint::from(DONATION_AMOUNT), + ) + .with_result(ExpectError( + 4, + "Already made a payment with a different TokenID", + )) + .run(); +} + +#[test] +fn test_fail_donate_to_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id = 1usize; + state.check_potlock_id_is_last(potlock_id); + + // Accept Pot + state.accept_pot(potlock_id); + + let new_project_id = state.apply_for_pot(potlock_id, "Project name", "Project description"); + state.check_project_id_is_last(new_project_id); + + state.accept_application(new_project_id); + state.check_project_is_accepted(ManagedBuffer::from("Project description")); + + state.check_esdt_balance(POT_PROPOSER_ADDRESS, INITIAL_BALANCE - POT_FEE_CREATION); + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION); + state.check_esdt_balance(POT_DONOR_ADDRESS, INITIAL_BALANCE); + + state.donate_to_pot(new_project_id, POT_TOKEN_ID); + + state.check_sc_esdt_balance(POTLOCK_ADDRESS, POT_FEE_CREATION + DONATION_AMOUNT); + state.check_esdt_balance(POT_DONOR_ADDRESS, INITIAL_BALANCE - DONATION_AMOUNT); + + state + .world + .tx() + .from(POT_DONOR_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .donate_to_pot(new_project_id) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(DIFFERENT_TOKEN_ID), + 0u64, + &multiversx_sc::proxy_imports::BigUint::from(DONATION_AMOUNT), + ) + .with_result(ExpectError( + 4, + "Already made a payment with a different TokenID", + )) + .run(); +} + +#[test] +fn test_fail_donate_to_non_active_pot() { + let mut state = PotlockTestState::new(); + state.deploy_potlock_contract(); + state.change_fee_for_pots(POT_FEE_CREATION); + + state.add_pot("Pot", "Pot Description"); + let potlock_id = 1usize; + state.check_potlock_id_is_last(potlock_id); + + state + .world + .tx() + .from(PROJECT_DONOR_ADDRESS) + .to(POTLOCK_ADDRESS) + .typed(potlock_proxy::PotlockProxy) + .donate_to_pot(potlock_id) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(DIFFERENT_TOKEN_ID), + 0u64, + &multiversx_sc::proxy_imports::BigUint::from(DONATION_AMOUNT), + ) + .with_result(ExpectError( + 4, + "Pot is not active!", + )) + .run(); +} diff --git a/contracts/potlock/tests/potlock_proxy.rs b/contracts/potlock/tests/potlock_proxy.rs new file mode 100644 index 00000000..687a6581 --- /dev/null +++ b/contracts/potlock/tests/potlock_proxy.rs @@ -0,0 +1,390 @@ +// Code generated by the multiversx-sc proxy generator. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +#![allow(dead_code)] +#![allow(clippy::all)] + +use multiversx_sc::proxy_imports::*; + +pub struct PotlockProxy; + +impl TxProxyTrait for PotlockProxy +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + type TxProxyMethods = PotlockProxyMethods; + + fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { + PotlockProxyMethods { wrapped_tx: tx } + } +} + +pub struct PotlockProxyMethods +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + wrapped_tx: Tx, +} + +#[rustfmt::skip] +impl PotlockProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + Gas: TxGas, +{ + pub fn init< + Arg0: ProxyArg>>, + >( + self, + admins: Arg0, + ) -> TxTypedDeploy { + self.wrapped_tx + .payment(NotPayable) + .raw_deploy() + .argument(&admins) + .original_result() + } +} + +#[rustfmt::skip] +impl PotlockProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn upgrade( + self, + ) -> TxTypedUpgrade { + self.wrapped_tx + .payment(NotPayable) + .raw_upgrade() + .original_result() + } +} + +#[rustfmt::skip] +impl PotlockProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn change_fee_for_pots< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + token_identifier: Arg0, + fee: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("changeFeeForPots") + .argument(&token_identifier) + .argument(&fee) + .original_result() + } + + pub fn accept_pot< + Arg0: ProxyArg, + >( + self, + potlock_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("acceptPot") + .argument(&potlock_id) + .original_result() + } + + pub fn remove_pot< + Arg0: ProxyArg, + >( + self, + potlock_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removePot") + .argument(&potlock_id) + .original_result() + } + + pub fn accept_application< + Arg0: ProxyArg, + >( + self, + project_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("acceptApplication") + .argument(&project_id) + .original_result() + } + + pub fn remove_application< + Arg0: ProxyArg, + >( + self, + project_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeApplication") + .argument(&project_id) + .original_result() + } + + pub fn reject_donation< + Arg0: ProxyArg, + Arg1: ProxyArg>, + >( + self, + potlock_id: Arg0, + user: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("rejectDonation") + .argument(&potlock_id) + .argument(&user) + .original_result() + } + + pub fn distribute_pot_to_projects< + Arg0: ProxyArg, + Arg1: ProxyArg>>, + >( + self, + potlock_id: Arg0, + project_percentages: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("distributePotToProjects") + .argument(&potlock_id) + .argument(&project_percentages) + .original_result() + } + + pub fn add_pot< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + name: Arg0, + description: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("addPot") + .argument(&name) + .argument(&description) + .original_result() + } + + pub fn apply_for_pot< + Arg0: ProxyArg, + Arg1: ProxyArg>, + Arg2: ProxyArg>, + >( + self, + potlock_id: Arg0, + project_name: Arg1, + description: Arg2, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("applyForPot") + .argument(&potlock_id) + .argument(&project_name) + .argument(&description) + .original_result() + } + + pub fn donate_to_pot< + Arg0: ProxyArg, + >( + self, + potlock_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("donateToPot") + .argument(&potlock_id) + .original_result() + } + + pub fn donate_to_project< + Arg0: ProxyArg, + >( + self, + project_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("donateToProject") + .argument(&project_id) + .original_result() + } + + pub fn fee_token_identifier( + self, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getFeeTokenIdentifier") + .original_result() + } + + pub fn fee_amount( + self, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getFeeAmount") + .original_result() + } + + pub fn potlocks( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getPotlocks") + .original_result() + } + + pub fn projects( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getProjects") + .original_result() + } + + pub fn pot_donations< + Arg0: ProxyArg, + >( + self, + potlock_id: Arg0, + ) -> TxTypedCall, EsdtTokenPayment>>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("potDonations") + .argument(&potlock_id) + .original_result() + } + + pub fn project_donations< + Arg0: ProxyArg, + >( + self, + project_id: Arg0, + ) -> TxTypedCall, EsdtTokenPayment>>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("projectDonations") + .argument(&project_id) + .original_result() + } + + pub fn is_admin< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("isAdmin") + .argument(&address) + .original_result() + } + + pub fn add_admin< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addAdmin") + .argument(&address) + .original_result() + } + + pub fn remove_admin< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeAdmin") + .argument(&address) + .original_result() + } + + pub fn admins( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getAdmins") + .original_result() + } +} + +#[type_abi] +#[derive(TopEncode, TopDecode)] +pub struct Pot +where + Api: ManagedTypeApi, +{ + pub potlock_id: usize, + pub proposer: ManagedAddress, + pub token_identifier: TokenIdentifier, + pub fee: BigUint, + pub name: ManagedBuffer, + pub description: ManagedBuffer, + pub status: Status, +} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedDecode, NestedEncode)] +pub enum Status { + Inactive, + Active, +} + +#[type_abi] +#[derive(TopEncode, TopDecode)] +pub struct Project +where + Api: ManagedTypeApi, +{ + pub potlock_id: usize, + pub name: ManagedBuffer, + pub description: ManagedBuffer, + pub owner: ManagedAddress, + pub status: Status, +} diff --git a/contracts/potlock/wasm/Cargo.lock b/contracts/potlock/wasm/Cargo.lock new file mode 100644 index 00000000..271f9870 --- /dev/null +++ b/contracts/potlock/wasm/Cargo.lock @@ -0,0 +1,198 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "multiversx-sc" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ea89a26f0aacda21437a8ae5ccfbefab99d8191942b3d2eddbcbf84f9866d7" +dependencies = [ + "bitflags", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d7a5a8534e5dc9128cb8f15a65a21dd378e135c6016c7cd1491cd012bc8cb" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dffba1dce273ed5b61ee1b90aeea5c8c744617d0f12624f620768c144d83e753" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c17fdf90fafca2f19085ae67b0502d9f71bf8ab1be3c83808eb88e02a8c18b9" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-modules" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeb48acbd39255868a3241798df2f85050f0ae8d82d6417bd2cd0e30a241855" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20659915a4377d375c46d7f237e810053a03f7e084fad6362dd5748a7233defb" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "potlock" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", +] + +[[package]] +name = "potlock-wasm" +version = "0.0.0" +dependencies = [ + "multiversx-sc-wasm-adapter", + "potlock", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unwrap-infallible" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" diff --git a/contracts/potlock/wasm/Cargo.toml b/contracts/potlock/wasm/Cargo.toml new file mode 100644 index 00000000..1f151346 --- /dev/null +++ b/contracts/potlock/wasm/Cargo.toml @@ -0,0 +1,34 @@ +# Code generated by the multiversx-sc build system. DO NOT EDIT. + +# ########################################## +# ############## AUTO-GENERATED ############# +# ########################################## + +[package] +name = "potlock-wasm" +version = "0.0.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[profile.release] +codegen-units = 1 +opt-level = "z" +lto = true +debug = false +panic = "abort" +overflow-checks = false + +[profile.dev] +panic = "abort" + +[dependencies.potlock] +path = ".." + +[dependencies.multiversx-sc-wasm-adapter] +version = "0.53.2" + +[workspace] +members = ["."] diff --git a/contracts/potlock/wasm/src/lib.rs b/contracts/potlock/wasm/src/lib.rs new file mode 100644 index 00000000..8bb6ebda --- /dev/null +++ b/contracts/potlock/wasm/src/lib.rs @@ -0,0 +1,47 @@ +// Code generated by the multiversx-sc build system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Upgrade: 1 +// Endpoints: 21 +// Async Callback (empty): 1 +// Total number of exported functions: 24 + +#![no_std] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + potlock + ( + init => init + upgrade => upgrade + changeFeeForPots => change_fee_for_pots + acceptPot => accept_pot + removePot => remove_pot + acceptApplication => accept_application + removeApplication => remove_application + rejectDonation => reject_donation + distributePotToProjects => distribute_pot_to_projects + addPot => add_pot + applyForPot => apply_for_pot + donateToPot => donate_to_pot + donateToProject => donate_to_project + getFeeTokenIdentifier => fee_token_identifier + getFeeAmount => fee_amount + getPotlocks => potlocks + getProjects => projects + potDonations => pot_donations + projectDonations => project_donations + isAdmin => is_admin + addAdmin => add_admin + removeAdmin => remove_admin + getAdmins => admins + ) +} + +multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/contracts/price-aggregator/wasm/src/lib.rs b/contracts/price-aggregator/wasm/src/lib.rs index 667c425a..a8e75f49 100644 --- a/contracts/price-aggregator/wasm/src/lib.rs +++ b/contracts/price-aggregator/wasm/src/lib.rs @@ -5,9 +5,10 @@ //////////////////////////////////////////////////// // Init: 1 +// Upgrade: 1 // Endpoints: 21 // Async Callback (empty): 1 -// Total number of exported functions: 23 +// Total number of exported functions: 24 #![no_std] @@ -18,6 +19,7 @@ multiversx_sc_wasm_adapter::endpoints! { multiversx_price_aggregator_sc ( init => init + upgrade => upgrade changeAmounts => change_amounts addOracles => add_oracles removeOracles => remove_oracles