From 802bbef44fb0168b101ea1fb12d1a9df5576d24b Mon Sep 17 00:00:00 2001 From: Nerrolol <122930773+Nerrolol@users.noreply.github.com> Date: Mon, 27 May 2024 18:03:59 +0200 Subject: [PATCH] Implement tests according to missing test list #58 (#61) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add custom proposal implementation Lacks setting of custom proposal configuration during deployment * Add arbitrary proposal * Polish apply_passed_proposal * Polish scarb fmt * Cherry pick setup.cairo from 1fab54f * Move test setup to src/ * Remove unrelated functions from setup.cairo * Fix setup * Polish tests * Add airdrop tests * Adding setup file with generic function which applies a proposal to upgrade root and passes it * Setup environment to deploy governance, deploy gov token and distribute to sample addresses and then vote on update root proposal * Add tests on proposals * fix some issues on proposal tests * Proposals Frontend (#66) * add: basic ui displaying proposals * feat: voting invocations * feat: :sparkles: new proposal form * feat: :sparkles: use async for proposal creation * fix: :bug: bad voting code * chore: :wastebasket: cleanup * feat: :sparkles: add logo * fix: :bug: wrong site title --------- Co-authored-by: Tomáš Hobza * Update README.md * Update README.md * feat: default to_upgrade value (#69) Co-authored-by: Tomáš Hobza * Telegram Notification bot for proposal (#71) * initial commit * change to testnet * Add basic working event printing * notification telegram bot working * add gitignore * Remove node_modules folder and update .gitignore * update config.toml * removing unused address * Delete accidentally committed tests/proposal.cairo * code review changes * rest of the code-review changes * Update packages according to code review feedback * Add error print when unable to send messages to Telegram * Add automatic retrieval of chain id * Polish formatting * Refactor config file reading * code-review changes in index.ts, package-lock * Add CI runner * Trigger CI * Fix CI * Fix CI * Fix CI * Fix CI --------- Co-authored-by: Ondřej Sojka Co-authored-by: Ondřej Sojka <8470346+tensojka@users.noreply.github.com> * Adding proposals tests * Comment code for airdrop_tests and remove old setup file * Add minor fixes towards compilability * Fix all proposals tests * Fix formatting * Proposals Frontend (#66) * add: basic ui displaying proposals * feat: voting invocations * feat: :sparkles: new proposal form * feat: :sparkles: use async for proposal creation * fix: :bug: bad voting code * chore: :wastebasket: cleanup * feat: :sparkles: add logo * fix: :bug: wrong site title --------- Co-authored-by: Tomáš Hobza * feat: default to_upgrade value (#69) Co-authored-by: Tomáš Hobza * Update frontend domain to konoha.vote * Rename package from 'governance' to 'konoha' * Add contributor guidelines * Update README.md with contributing info * Update CONTRIBUTING.md * Add mdbook scaffolding (#75) * Add mdbook stub * Update license to Apache 2.0 * Add some docs * Add custom proposal implementation (#64) * Add custom proposal implementation Lacks setting of custom proposal configuration during deployment * Add arbitrary proposal * Polish apply_passed_proposal * Polish scarb fmt * Cherry pick setup.cairo from 1fab54f * Move test setup to src/ * Remove unrelated functions from setup.cairo * Fix setup * Polish tests * Fix import naming * Add addition of custom proposal * Remove mentions of Carmine in this repository (#73) * Update Scarb.toml, add [lib] * Add custom proposal implementation Lacks setting of custom proposal configuration during deployment * Add arbitrary proposal * Polish apply_passed_proposal * Polish scarb fmt * Cherry pick setup.cairo from 1fab54f * Move test setup to src/ * Remove unrelated functions from setup.cairo * Fix setup * Polish tests * Fix import naming * Add addition of custom proposal * Remove part of Carmine code * Remove rest of Carmine stuff * Polish with scarb fmt * Bump version to 0.4.0 * Fix imports in tests * Remove unused test from lib.cairo * Remove cubit as dependency * Update package description * Update error message to not mention CARM * Move snforge_std to dev-dependency * Update snforge_std to v0.23.0 * Update snforge in CI * Trigger CI * Format * Rename governance:: to konoha:: * Fix proposals tests and mint tokens to admin addr before distributing them in deploy token function * Add quorum tests * Move setup to testing, fix tests so they compile * Merge branch master --------- Co-authored-by: Ondřej Sojka Co-authored-by: Tomáš Hobza Co-authored-by: Tomáš Hobza Co-authored-by: Ondřej Sojka <8470346+tensojka@users.noreply.github.com> Co-authored-by: xkrivan5 <116671001+xkrivan5@users.noreply.github.com> --- Scarb.toml | 7 ++ src/lib.cairo | 5 -- tests/airdrop_tests.cairo | 87 +++++++++++++++++++ tests/basic.cairo | 2 +- tests/lib.cairo | 4 +- tests/proposals_tests.cairo | 164 ++++++++++++++++++++++++++++++++++++ tests/setup.cairo | 83 ++++++++++++++++++ 7 files changed, 345 insertions(+), 7 deletions(-) create mode 100644 tests/airdrop_tests.cairo create mode 100644 tests/proposals_tests.cairo create mode 100644 tests/setup.cairo diff --git a/Scarb.toml b/Scarb.toml index 72b1bba9..c44d593c 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -15,6 +15,13 @@ snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag [lib] + +# can be fixed by doing import super::testing from tests +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.23.0" } + +[lib] + [[target.starknet-contract]] [scripts] diff --git a/src/lib.cairo b/src/lib.cairo index d3bdfcbd..4a3d1500 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -5,7 +5,6 @@ mod treasury_types { mod constants; mod contract; mod merkle_tree; -//mod options; mod proposals; mod token; mod traits; @@ -13,8 +12,4 @@ mod treasury; mod types; mod upgrades; mod vesting; -mod govtoken; // if I put this in tests/ , I seem unable to use declare('MyToken') mod voting_token; -mod testing { - mod setup; -} diff --git a/tests/airdrop_tests.cairo b/tests/airdrop_tests.cairo new file mode 100644 index 00000000..4a9c1c8f --- /dev/null +++ b/tests/airdrop_tests.cairo @@ -0,0 +1,87 @@ +// use core::hash::HashStateExTrait; +// use core::{ArrayTrait, SpanTrait}; +// use core::debug::PrintTrait; +// use governance::airdrop::{airdrop, IAirdropDispatcher, IAirdropDispatcherTrait}; +// use airdrop::STRK_ADDRESS; +// use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +// use snforge_std::{ContractClassTrait, declare, start_prank, CheatTarget}; +// use starknet::{ContractAddress, deploy_syscall}; + +// const ADMIN_ADDR: felt252 = 0x42; +// const CLAIMEE_1: felt252 = 0x13; +// const CLAIMEE_2: felt252 = 0x14; + +// fn deploy() -> IAirdropDispatcher { +// let mut calldata = ArrayTrait::new(); +// calldata.append(ADMIN_ADDR); + +// let contract = declare('Airdrop'); +// let address = contract.deploy().expect('unable to deploy Airdrop'); +// IAirdropDispatcher { contract_address: address } +// } + +// #[test] +// fn test_claim_twice_with_same_proof() { +// let airdrop_contract = deploy(); +// let token_contract = deploy_token(airdrop_contract.contract_address); + +// start_prank( +// CheatTarget::One(airdrop_contract.contract_address), ADMIN_ADDR.try_into().unwrap() +// ); +// airdrop_contract.add_root(valid_root); + +// start_prank(CheatTarget::One(airdrop_contract.contract_address), CLAIMEE_1.try_into().unwrap()); +// let initial_proof = array![valid_proof_element]; +// airdrop_contract.claim(CLAIMEE_1, valid_claim_amount, initial_proof.span()); +// assert( +// token_contract.balance_of(CLAIMEE_1.try_into().unwrap()) == valid_claim_amount, +// "First claim failed" +// ); + +// airdrop_contract.claim(CLAIMEE_1, valid_claim_amount, initial_proof.span()); +// assert( +// token_contract.balance_of(CLAIMEE_1.try_into().unwrap()) == valid_claim_amount, +// "Second claim modified the claimee's balance" +// ); +// } + +// #[test] +// #[should_panic(expected: ('INVALID PROOF',))] +// fn test_claim_invalid_proof() { +// let contract = deploy(); +// deploy_token(contract.contract_address); +// start_prank(CheatTarget::One(contract.contract_address), ADMIN_ADDR.try_into().unwrap()); +// contract.add_root(0xf7c8d3f309262572ad35df8ff6c33f24d8114c60eac3bc27bf42382ca82faf); + +// start_prank(CheatTarget::One(contract.contract_address), CLAIMEE_1.try_into().unwrap()); +// let proof = array![0x2a18afb0550a011d54ca3940648e59894c06e4c3d0a611256c0b575bd528b3b, 0x1]; +// contract.claim(0x88, proof.span()); +// } + +// #[test] +// fn test_update_root_and_claim_attempts() { +// let contract = deploy(); +// let tok = deploy_token(contract.contract_address); +// start_prank(CheatTarget::One(contract.contract_address), ADMIN_ADDR.try_into().unwrap()); + +// // add intial root and valid claim +// contract.add_root(initial_root); +// start_prank(CheatTarget::One(contract.contract_address), CLAIMEE_1.try_into().unwrap()); +// contract.claim(claim_amount, valid_proof_for_initial_root.span()); +// assert(tok.balance_of(CLAIMEE_1.try_into().unwrap()) == claim_amount, 'initial claim failed'); + +// // update root +// start_prank(CheatTarget::One(contract.contract_address), ADMIN_ADDR.try_into().unwrap()); +// contract.add_root(new_root); + +// // claim with old root + new proof, should fail +// start_prank(CheatTarget::One(contract.contract_address), CLAIMEE_2.try_into().unwrap()); +// contract.claim(claim_amount, new_proof_not_matching_old_root.span()); +// // check fail : to do, use should panic ? + +// // claim with new root + old proof, should fail +// contract.claim(claim_amount, old_proof_not_matching_new_root.span()); +// // check fail : to do +// } + + diff --git a/tests/basic.cairo b/tests/basic.cairo index 0880888c..c9de1d6d 100644 --- a/tests/basic.cairo +++ b/tests/basic.cairo @@ -21,7 +21,7 @@ fn test_submit_proposal() { let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; // corresponding govtoken: 0x05151bfdd47826df3669033ea7fb977d3b2d45c4f4d1c439a9edf4062bf34bfa // has one holder, with 31 CARM: 0x0583a9d956d65628f806386ab5b12dccd74236a3c6b930ded9cf3c54efc722a1 - let admin_addr: ContractAddress = + let _admin_addr: ContractAddress = 0x0583a9d956d65628f806386ab5b12dccd74236a3c6b930ded9cf3c54efc722a1 .try_into() .unwrap(); diff --git a/tests/lib.cairo b/tests/lib.cairo index f671d39b..4feb9077 100644 --- a/tests/lib.cairo +++ b/tests/lib.cairo @@ -1,3 +1,5 @@ -mod vesting; mod basic; mod test_treasury; +mod proposals_tests; +mod airdrop_tests; +mod setup; diff --git a/tests/proposals_tests.cairo b/tests/proposals_tests.cairo new file mode 100644 index 00000000..80df3d78 --- /dev/null +++ b/tests/proposals_tests.cairo @@ -0,0 +1,164 @@ +use array::ArrayTrait; +use core::traits::TryInto; +use debug::PrintTrait; +use starknet::ContractAddress; +use openzeppelin::token::erc20::interface::{ + IERC20Dispatcher, IERC20DispatcherTrait, IERC20CamelOnlyDispatcher, + IERC20CamelOnlyDispatcherTrait +}; +use snforge_std::{ + BlockId, declare, ContractClassTrait, ContractClass, start_prank, start_warp, CheatTarget, + prank, CheatSpan +}; + +use super::setup::{ + admin_addr, first_address, second_address, deploy_governance, deploy_and_distribute_gov_tokens, + test_vote_upgrade_root, check_if_healthy +}; +use konoha::contract::IGovernanceDispatcher; +use konoha::contract::IGovernanceDispatcherTrait; +use konoha::proposals::IProposalsDispatcher; +use konoha::proposals::IProposalsDispatcherTrait; +use konoha::upgrades::IUpgradesDispatcher; +use konoha::upgrades::IUpgradesDispatcherTrait; +use konoha::constants; +use starknet::get_block_timestamp; + + +const GOV_TOKEN_INITIAL_SUPPLY: felt252 = 1000000000000000000; + + +fn test_express_proposal() { + let token_contract = deploy_and_distribute_gov_tokens(admin_addr.try_into().unwrap()); + let gov_contract = deploy_governance(token_contract.contract_address); + let gov_contract_addr = gov_contract.contract_address; + + let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; + + start_prank(CheatTarget::One(gov_contract_addr), admin_addr.try_into().unwrap()); + let prop_id = dispatcher.submit_proposal(42, 1); + + start_prank(CheatTarget::One(gov_contract_addr), admin_addr.try_into().unwrap()); + dispatcher.vote(prop_id, 1); + + assert!(dispatcher.get_proposal_status(prop_id) == 1, "proposal not passed!"); +} + +#[test] +fn test_proposal_expiry() { + let token_contract = deploy_and_distribute_gov_tokens(admin_addr.try_into().unwrap()); + let gov_contract = deploy_governance(token_contract.contract_address); + let gov_contract_addr = gov_contract.contract_address; + + let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; + + start_prank(CheatTarget::One(gov_contract_addr), admin_addr.try_into().unwrap()); + let prop_id = dispatcher.submit_proposal(42, 1); + + //simulate passage of time + let current_timestamp = get_block_timestamp(); + let end_timestamp = current_timestamp + constants::PROPOSAL_VOTING_SECONDS; + start_warp(CheatTarget::One(gov_contract_addr), end_timestamp + 1); + + let status = dispatcher.get_proposal_status(prop_id); + assert!(status == constants::MINUS_ONE, "proposal not expired!"); +} + +#[test] +#[should_panic(expected: ('voting concluded',))] +fn test_vote_on_expired_proposal() { + let token_contract = deploy_and_distribute_gov_tokens(admin_addr.try_into().unwrap()); + let gov_contract = deploy_governance(token_contract.contract_address); + let gov_contract_addr = gov_contract.contract_address; + + let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; + + start_prank(CheatTarget::One(gov_contract_addr), admin_addr.try_into().unwrap()); + let prop_id = dispatcher.submit_proposal(42, 1); + + //simulate passage of time + let current_timestamp = get_block_timestamp(); + let end_timestamp = current_timestamp + constants::PROPOSAL_VOTING_SECONDS; + start_warp(CheatTarget::One(gov_contract_addr), end_timestamp + 1); + + prank( + CheatTarget::One(token_contract.contract_address), + admin_addr.try_into().unwrap(), + CheatSpan::TargetCalls(1) + ); + token_contract.transfer(first_address.try_into().unwrap(), 100000.try_into().unwrap()); + start_prank(CheatTarget::One(gov_contract_addr), first_address.try_into().unwrap()); + dispatcher.vote(prop_id, 1); +} + +#[test] +fn test_vote_on_quorum_not_met() { + let token_contract = deploy_and_distribute_gov_tokens(admin_addr.try_into().unwrap()); + let gov_contract = deploy_governance(token_contract.contract_address); + let gov_contract_addr = gov_contract.contract_address; + + let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; + + prank( + CheatTarget::One(gov_contract_addr), + admin_addr.try_into().unwrap(), + CheatSpan::TargetCalls(1) + ); + let prop_id = dispatcher.submit_proposal(42, 1); + + prank( + CheatTarget::One(token_contract.contract_address), + admin_addr.try_into().unwrap(), + CheatSpan::TargetCalls(1) + ); + token_contract.transfer(first_address.try_into().unwrap(), 100000.try_into().unwrap()); + + prank( + CheatTarget::One(gov_contract_addr), + first_address.try_into().unwrap(), + CheatSpan::TargetCalls(1) + ); + dispatcher.vote(prop_id, 1); + + let (yay_votes, nay_votes) = dispatcher.get_vote_counts(prop_id); + let total_votes = yay_votes + nay_votes; + let total_eligible_votes: u128 = IERC20CamelOnlyDispatcher { + contract_address: token_contract.contract_address + } + .totalSupply() + .low; + let quorum_threshold = total_eligible_votes * constants::QUORUM / 100; + + assert(total_votes < quorum_threshold, 'Total votes >= quorum threshold'); + let current_timestamp = get_block_timestamp(); + let end_timestamp = current_timestamp + constants::PROPOSAL_VOTING_SECONDS; + start_warp(CheatTarget::One(gov_contract_addr), end_timestamp + 1); + assert( + dispatcher.get_proposal_status(prop_id) == constants::MINUS_ONE, + 'Proposal pass & quorum not met' + ); +} + +#[test] +#[should_panic(expected: ('not enough tokens to submit',))] +fn test_submit_proposal_under_quorum() { + let token_contract = deploy_and_distribute_gov_tokens(admin_addr.try_into().unwrap()); + let gov_contract = deploy_governance(token_contract.contract_address); + let gov_contract_addr = gov_contract.contract_address; + + let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; + + prank( + CheatTarget::One(token_contract.contract_address), + admin_addr.try_into().unwrap(), + CheatSpan::TargetCalls(1) + ); + token_contract.transfer(first_address.try_into().unwrap(), 100000.try_into().unwrap()); + + prank( + CheatTarget::One(gov_contract_addr), + first_address.try_into().unwrap(), + CheatSpan::TargetCalls(1) + ); + dispatcher.submit_proposal(42, 1); +} diff --git a/tests/setup.cairo b/tests/setup.cairo new file mode 100644 index 00000000..f6d09b76 --- /dev/null +++ b/tests/setup.cairo @@ -0,0 +1,83 @@ +use core::traits::Into; +use array::ArrayTrait; +use core::traits::TryInto; +use debug::PrintTrait; +use starknet::ContractAddress; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use snforge_std::{ + BlockId, declare, ContractClassTrait, ContractClass, start_prank, start_warp, CheatTarget +}; +use core::ResultTrait; + + +use konoha::contract::IGovernanceDispatcher; +use konoha::contract::IGovernanceDispatcherTrait; +use konoha::proposals::IProposalsDispatcher; +use konoha::proposals::IProposalsDispatcherTrait; +use konoha::upgrades::IUpgradesDispatcher; +use konoha::upgrades::IUpgradesDispatcherTrait; +use konoha::constants; +use openzeppelin::token::erc20::interface::IERC20; +use starknet::get_block_timestamp; + + +const GOV_TOKEN_INITIAL_SUPPLY: u256 = 1000000000000000000; + +const first_address: felt252 = 0x1; +const second_address: felt252 = 0x2; +const admin_addr: felt252 = 0x3; + +fn deploy_governance(token_address: ContractAddress) -> IGovernanceDispatcher { + let gov_contract = declare("Governance").expect('unable to declare governance'); + let mut args: Array = ArrayTrait::new(); + args.append(token_address.into()); + let (address, _) = gov_contract.deploy(@args).expect('unable to deploy governance'); + IGovernanceDispatcher { contract_address: address } +} + + +fn deploy_and_distribute_gov_tokens(recipient: ContractAddress) -> IERC20Dispatcher { + let mut calldata = ArrayTrait::new(); + calldata.append(GOV_TOKEN_INITIAL_SUPPLY.low.into()); + calldata.append(GOV_TOKEN_INITIAL_SUPPLY.high.into()); + calldata.append(recipient.into()); + + let gov_token_contract = declare("FloatingToken").expect('unable to declare FloatingToken'); + let (token_addr, _) = gov_token_contract + .deploy(@calldata) + .expect('unable to deploy FloatingToken'); + IERC20Dispatcher { contract_address: token_addr } +} + + +fn test_vote_upgrade_root(new_merkle_root: felt252) { + let token_contract = deploy_and_distribute_gov_tokens(admin_addr.try_into().unwrap()); + let gov_contract = deploy_governance(token_contract.contract_address); + let gov_contract_addr = gov_contract.contract_address; + + let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; + + start_prank(CheatTarget::One(gov_contract_addr), admin_addr.try_into().unwrap()); + let prop_id = dispatcher.submit_proposal(new_merkle_root, 3); + + start_prank(CheatTarget::One(gov_contract_addr), first_address.try_into().unwrap()); + dispatcher.vote(prop_id, 1); + start_prank(CheatTarget::One(gov_contract_addr), second_address.try_into().unwrap()); + dispatcher.vote(prop_id, 1); + start_prank(CheatTarget::One(gov_contract_addr), admin_addr.try_into().unwrap()); + dispatcher.vote(prop_id, 1); + + assert(dispatcher.get_proposal_status(prop_id) == 1, 'proposal not passed!'); + + let upgrade_dispatcher = IUpgradesDispatcher { contract_address: gov_contract_addr }; + upgrade_dispatcher.apply_passed_proposal(prop_id); + assert(check_if_healthy(gov_contract_addr), 'new gov not healthy'); +} + +fn check_if_healthy(gov_contract_addr: ContractAddress) -> bool { + // TODO + let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; + dispatcher.get_proposal_status(0); + let prop_details = dispatcher.get_proposal_details(0); + (prop_details.payload + prop_details.to_upgrade) != 0 +}