diff --git a/Scarb.toml b/Scarb.toml index 3cf9473..e1bebdc 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -22,10 +22,10 @@ sort-module-level-items = true [[tool.snforge.fork]] name = "MAINNET" -url = "http://34.22.208.73:6060/v0_7" +url = "http://178.32.172.148:6060/v0_7" block_id.tag = "Latest" [[tool.snforge.fork]] name = "SEPOLIA" -url = "http://34.22.208.73:6062/v0_7" +url = "http://178.32.172.148:6062/v0_7" block_id.tag = "Latest" diff --git a/src/carm.cairo b/src/carm.cairo index 3046676..b172b41 100644 --- a/src/carm.cairo +++ b/src/carm.cairo @@ -62,6 +62,7 @@ mod CRMToken { self.erc20.initializer(name, symbol); self.erc20._mint(recipient, fixed_supply); + self.ownable.initializer(owner); } #[abi(embed_v0)] diff --git a/src/constants.cairo b/src/constants.cairo index 18d9e8a..15eb0a1 100644 --- a/src/constants.cairo +++ b/src/constants.cairo @@ -13,4 +13,4 @@ pub const AMM_CLASS_HASH: felt252 = pub const OPTION_TOKEN_CLASS_HASH: felt252 = 0x07fc0b6ecc96a698cdac8c4ae447816d73bffdd9603faacffc0a8047149d02ed; -pub const UNLOCK_DATE: u64 = 1720915199; // Sat Jul 13 2024 23:59:59 GMT+0000 +pub const UNLOCK_DATE: u64 = 1719838800; // Mon Jul 01 2024 13:00:00 GMT+0000 diff --git a/src/contract.cairo b/src/contract.cairo index 05721c7..9ea25f9 100644 --- a/src/contract.cairo +++ b/src/contract.cairo @@ -20,6 +20,8 @@ pub mod Governance { use konoha::airdrop::airdrop as airdrop_component; use konoha::types::{BlockNumber, VoteStatus, ContractType, PropDetails, CustomProposalConfig}; + use starknet::syscalls::deploy_syscall; + use starknet::{ContractAddress, get_contract_address, ClassHash}; @@ -66,45 +68,13 @@ pub mod Governance { StakingEvent: staking_component::Event, } - use starknet::syscalls::deploy_syscall; - #[constructor] - fn constructor(ref self: ContractState, + fn constructor( + ref self: ContractState, voting_token_class: ClassHash, floating_token_class: ClassHash, - recipient: ContractAddress) { - // This is not used in production on mainnet, because the governance token is already deployed (and distributed). - - let governance_address = get_contract_address(); - - let mut voting_token_calldata: Array = ArrayTrait::new(); - voting_token_calldata.append(governance_address.into()); - let (voting_token_address, _) = deploy_syscall( - voting_token_class, 42, voting_token_calldata.span(), true - ) - .unwrap(); - self.governance_token_address.write(voting_token_address); - - let mut floating_token_calldata: Array = ArrayTrait::new(); - floating_token_calldata.append(10000000000000000000); // 10**19, 10 tokens overall - floating_token_calldata.append(0); // high for u256 supply - floating_token_calldata.append(recipient.into()); - floating_token_calldata.append(governance_address.into()); - let (floating_token_address, _) = deploy_syscall( - floating_token_class, 42, floating_token_calldata.span(), true - ) - .unwrap(); - - let staking = IStakingDispatcher { contract_address: governance_address }; - staking.set_floating_token_address(floating_token_address); - let ONE_MONTH: u64 = 2629743; // 30.44 days - let THREE_MONTHS = ONE_MONTH * 3; - let SIX_MONTHS = ONE_MONTH * 6; - let ONE_YEAR: u64 = 31536000; // 365 days - staking.set_curve_point(ONE_MONTH, 100); - staking.set_curve_point(THREE_MONTHS, 120); - staking.set_curve_point(SIX_MONTHS, 160); - staking.set_curve_point(ONE_YEAR, 250); + recipient: ContractAddress + ) { // This is not used in production on mainnet, because the governance token is already deployed (and distributed). } #[abi(embed_v0)] @@ -132,7 +102,7 @@ pub mod Governance { target: self.amm_address.read().into(), selector: selector!("upgrade"), library_call: false - }; // TODO test + }; let upgrade_govtoken = CustomProposalConfig { target: self.governance_token_address.read().into(), selector: selector!("upgrade"), diff --git a/src/proposals.cairo b/src/proposals.cairo index be853cf..732e5f9 100644 --- a/src/proposals.cairo +++ b/src/proposals.cairo @@ -416,7 +416,7 @@ pub(crate) mod proposals { let current_timestamp: u64 = get_block_timestamp(); if current_timestamp <= end_timestamp { - return self.check_proposal_passed_express(prop_id).into(); + return 0; } let gov_token_addr = get_governance_token_address_self(); diff --git a/src/staking.cairo b/src/staking.cairo index 492b958..293d073 100644 --- a/src/staking.cairo +++ b/src/staking.cairo @@ -127,12 +127,32 @@ pub(crate) mod staking { let arr = array![ 0x0583a9d956d65628f806386ab5b12dccd74236a3c6b930ded9cf3c54efc722a1, 0x06717eaf502baac2b6b2c6ee3ac39b34a52e726a73905ed586e757158270a0af, + 0x058d2ddce3e4387dc0da7e45c291cb436bb809e00a4c132bcc5758e4574f55c7, + 0x05e61dfb8a9863e446981e804a203a7ad3a2d15495c85b79cfd053ec63e9bfb3, + 0x04379c63976feaca8019db2c08f7af8e976b11aef7eda9dfe2ef604e76fc99d2, 0x0011d341c6e841426448ff39aa443a6dbb428914e05ba2259463c18308b86233, 0x00d79a15d84f5820310db21f953a0fae92c95e25d93cb983cc0c27fc4c52273c, 0x03d1525605db970fa1724693404f5f64cba8af82ec4aab514e6ebd3dec4838ad, 0x06fd0529AC6d4515dA8E5f7B093e29ac0A546a42FB36C695c8f9D13c5f787f82, 0x04d2FE1Ff7c0181a4F473dCd982402D456385BAE3a0fc38C49C0A99A620d1abe, - 0x062c290f0afa1ea2d6b6d11f6f8ffb8e626f796e13be1cf09b84b2edaa083472 + 0x062c290f0afa1ea2d6b6d11f6f8ffb8e626f796e13be1cf09b84b2edaa083472, + 0x01714ab9a05b062e0c09cf635fd469ce664c914ef9d9ff2394928e31707ce9a6, + 0x06c59d2244250f2540a2694472e3c31262e887ff02582ef864bf0e76c34e1298, + 0x0528f064c43e2d6Ee73bCbfB725bAa293CD31Ea1f1861EA2F80Bc283Ea4Ad728, + 0x05105649f42252f79109356e1c8765b7dcdb9bf4a6a68534e7fc962421c7efd2, + 0x00777558f1c767126461540d1f10118981d30bd620707e99686bfc9f00ae66f0, + 0x06e2c2a5da2e5478b1103d452486afba8378e91f32a124f0712f09edd3ccd923, + 0x035e0845154423c485e5216f70496130079b5ddc8ac66e3e316184482788e2a0, + 0x0244dda2c6581eb158db225992153c9d49e92c412424daeb83a773fa9822eeef, // team multisig + ]; + @arr.span() + } + + #[inline(always)] + fn get_investor_addresses() -> @Span { + let arr = array![ + 0x05a4523982b437aadd1b5109b6618c46f7b1c42f5f9e7de1a3b84091f87d411b, + 0x056d761e1e5d1918dba05de02afdbd8de8da01a63147dce828c9b1fe9227077d, // investor multisig ]; @arr.span() } @@ -150,6 +170,19 @@ pub(crate) mod staking { } } + fn is_investor(potential_investor_address: ContractAddress) -> bool { + let potential_address: felt252 = potential_investor_address.into(); + let mut investor_addresses = *get_investor_addresses(); + loop { + match investor_addresses.pop_front() { + Option::Some(addr) => { if (*addr == potential_address) { + break true; + } }, + Option::None(_) => { break false; } + } + } + } + #[embeddable_as(StakingImpl)] impl Staking< TContractState, +HasComponent, @@ -249,7 +282,7 @@ pub(crate) mod staking { fn unstake_airdrop(ref self: ComponentState) { let caller = get_caller_address(); - if (is_team(caller)) { + if (is_team(caller) || is_investor(caller)) { assert(get_block_timestamp() > UNLOCK_DATE, 'tokens not yet unlocked'); } @@ -301,9 +334,9 @@ pub(crate) mod staking { let curr = self.floating_token_address.read(); assert(curr.into() == 0, 'floating token already init'); let default_address: ContractAddress = - 0x71cc3fbda6eb62d60c57c84eb995338fcb74a31dfb58e64f88185d1ac8ae8b8 + 0x051c4b1fe3bf6774b87ad0b15ef5d1472759076e42944fff9b9f641ff13e5bbe .try_into() - .unwrap(); // TODO fix + .unwrap(); self.floating_token_address.write(default_address); } @@ -339,7 +372,9 @@ pub(crate) mod staking { self: @ComponentState, address: ContractAddress ) -> u128 { let nonadjusted_voting_power = self.get_total_voting_power(address); - if (!is_team(address)) { + let is_investor = is_investor(address); + let is_team = is_team(address); + if (!is_investor && !is_team) { return nonadjusted_voting_power; } let total_supply: u128 = IERC20Dispatcher { @@ -348,12 +383,18 @@ pub(crate) mod staking { .total_supply() .try_into() .unwrap(); - let total_team = self.get_total_team_voting_power(); // 7 - let max_team_supply = (total_supply-total_team) / 2; // (8-7) / 2 = 0.5 - if (total_team < max_team_supply) { + let total_team = self.get_total_group_voting_power(false); + let total_investor = self.get_total_group_voting_power(true); + let max_group_supply = ((total_supply - total_team) - total_investor) / 2; + let total_group = if is_investor { + total_investor + } else { + total_team + }; + if (total_group < max_group_supply) { return nonadjusted_voting_power; } - let adj_factor = (TWO_POW_32 * max_team_supply) / total_team; + let adj_factor = (TWO_POW_32 * max_group_supply) / total_group; (adj_factor * nonadjusted_voting_power) / TWO_POW_32 } } @@ -396,11 +437,17 @@ pub(crate) mod staking { } } - fn get_total_team_voting_power(self: @ComponentState) -> u128 { + fn get_total_group_voting_power( + self: @ComponentState, investors: bool + ) -> u128 { let mut total: u128 = 0; - let mut team_addresses = *get_team_addresses(); + let mut addresses = if investors { + *get_investor_addresses() + } else { + *get_team_addresses() + }; loop { - match team_addresses.pop_front() { + match addresses.pop_front() { Option::Some(addr) => { total += self.get_total_voting_power((*addr).try_into().unwrap()); }, diff --git a/src/traits.cairo b/src/traits.cairo index 068c1d0..c0108d6 100644 --- a/src/traits.cairo +++ b/src/traits.cairo @@ -267,3 +267,25 @@ pub trait IOptionToken { fn maturity(self: @TState) -> u64; fn side(self: @TState) -> u8; } + +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn decimals(self: @TContractState) -> u8; + fn totalSupply(self: @TContractState) -> u256; + fn total_supply(self: @TContractState) -> u256; + fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; + fn transferFrom( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256); + fn burn(ref self: TContractState, recipient: ContractAddress, amount: u256); +} diff --git a/src/vecarm.cairo b/src/vecarm.cairo index afd6223..d50965c 100644 --- a/src/vecarm.cairo +++ b/src/vecarm.cairo @@ -7,7 +7,7 @@ pub trait IVeCRM { #[starknet::contract] pub mod VeCRM { - use konoha::traits::IERC20; + use amm_governance::traits::IERC20; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::access::ownable::interface::IOwnableTwoStep; use openzeppelin::access::ownable::ownable::OwnableComponent::InternalTrait as OwnableInternalTrait; @@ -73,12 +73,12 @@ pub mod VeCRM { #[abi(embed_v0)] impl VotingToken of IERC20 { // READ - fn name(self: @ContractState) -> ByteArray { - ERC20MetadataImpl::name(self) + fn name(self: @ContractState) -> felt252 { + 'vote escrowed Carmine token' } - fn symbol(self: @ContractState) -> ByteArray { - ERC20MetadataImpl::symbol(self) + fn symbol(self: @ContractState) -> felt252 { + 'veCRM' } fn decimals(self: @ContractState) -> u8 { diff --git a/tests/unstake_airdrop.cairo b/tests/unstake_airdrop.cairo index 85c644f..1eb7467 100644 --- a/tests/unstake_airdrop.cairo +++ b/tests/unstake_airdrop.cairo @@ -43,20 +43,18 @@ fn test_unstake_airdrop() { .try_into() .unwrap(); - let gov_class: ContractClass = declare("Governance").expect('unable to declare gov'); - let floating_class: ContractClass = declare("CRMToken").expect('unable to declare CRM'); - let voting_class: ContractClass = declare("VeCRM").expect('unable to declare voting'); + //let gov_class: ContractClass = declare("Governance").expect('unable to declare gov'); + //let floating_class: ContractClass = declare("CRMToken").expect('unable to declare CRM'); + //let voting_class: ContractClass = declare("VeCRM").expect('unable to declare voting'); let mut floating_calldata = ArrayTrait::new(); floating_calldata.append(10000000000000000000000000); // fixed supply low floating_calldata.append(0); // fixed supply high floating_calldata.append(gov_addr.into()); floating_calldata.append(gov_addr.into()); - let (floating_addr, _) = floating_class - .deploy_at( - @floating_calldata, - 0x71cc3fbda6eb62d60c57c84eb995338fcb74a31dfb58e64f88185d1ac8ae8b8.try_into().unwrap() - ) + let floating_addr: ContractAddress = + 0x051c4b1fe3bf6774b87ad0b15ef5d1472759076e42944fff9b9f641ff13e5bbe + .try_into() .unwrap(); let time_zero = get_block_timestamp(); @@ -86,11 +84,12 @@ fn test_unstake_airdrop() { prank(CheatTarget::One(gov_addr), team_member_1, CheatSpan::TargetCalls(6)); // Upgrade governance - let prop_id_gov_upgrade = props.submit_proposal(gov_class.class_hash.into(), 1); + let prop_id_gov_upgrade = props + .submit_proposal(0x04bc8bc7c476c4fca95624809dab1f1aa718edb566184a9d6dfe54f65b32b507, 1); props.vote(prop_id_gov_upgrade, 1); // Upgrade veCarm token - let prop_id_vecarm_upgrade = props.submit_proposal(voting_class.class_hash.into(), 2); + let prop_id_vecarm_upgrade = props.submit_proposal(0x008e98fd1f76f0d6fca8b03292e1dd6c8a6c5362f5aa0fd1186592168e9ad692, 2); props.vote(prop_id_vecarm_upgrade, 1); // Propose Airdrop @@ -197,20 +196,18 @@ fn test_unstake_airdrop_unstake_again_failing() { .try_into() .unwrap(); - let gov_class: ContractClass = declare("Governance").expect('unable to declare gov'); - let floating_class: ContractClass = declare("CRMToken").expect('unable to declare CRM'); - let voting_class: ContractClass = declare("VeCRM").expect('unable to declare voting'); + //let gov_class: ContractClass = declare("Governance").expect('unable to declare gov'); + //let floating_class: ContractClass = declare("CRMToken").expect('unable to declare CRM'); + //let voting_class: ContractClass = declare("VeCRM").expect('unable to declare voting'); let mut floating_calldata = ArrayTrait::new(); floating_calldata.append(10000000000000000000000000); // fixed supply low floating_calldata.append(0); // fixed supply high floating_calldata.append(gov_addr.into()); floating_calldata.append(gov_addr.into()); - let (floating_addr, _) = floating_class - .deploy_at( - @floating_calldata, - 0x71cc3fbda6eb62d60c57c84eb995338fcb74a31dfb58e64f88185d1ac8ae8b8.try_into().unwrap() - ) + let floating_addr: ContractAddress = + 0x051c4b1fe3bf6774b87ad0b15ef5d1472759076e42944fff9b9f641ff13e5bbe + .try_into() .unwrap(); let time_zero = get_block_timestamp(); @@ -228,11 +225,12 @@ fn test_unstake_airdrop_unstake_again_failing() { prank(CheatTarget::One(gov_addr), team_member_1, CheatSpan::TargetCalls(6)); // Upgrade governance - let prop_id_gov_upgrade = props.submit_proposal(gov_class.class_hash.into(), 1); + let prop_id_gov_upgrade = props + .submit_proposal(0x04bc8bc7c476c4fca95624809dab1f1aa718edb566184a9d6dfe54f65b32b507, 1); props.vote(prop_id_gov_upgrade, 1); // Upgrade veCarm token - let prop_id_vecarm_upgrade = props.submit_proposal(voting_class.class_hash.into(), 2); + let prop_id_vecarm_upgrade = props.submit_proposal(0x008e98fd1f76f0d6fca8b03292e1dd6c8a6c5362f5aa0fd1186592168e9ad692, 2); props.vote(prop_id_vecarm_upgrade, 1); // Propose Airdrop @@ -300,20 +298,18 @@ fn test_unstake_airdrop_team_member_failing() { .try_into() .unwrap(); - let gov_class: ContractClass = declare("Governance").expect('unable to declare gov'); - let floating_class: ContractClass = declare("CRMToken").expect('unable to declare CRM'); - let voting_class: ContractClass = declare("VeCRM").expect('unable to declare voting'); + //let gov_class: ContractClass = declare("Governance").expect('unable to declare gov'); + //let floating_class: ContractClass = declare("CRMToken").expect('unable to declare CRM'); + //let voting_class: ContractClass = declare("VeCRM").expect('unable to declare voting'); let mut floating_calldata = ArrayTrait::new(); floating_calldata.append(10000000000000000000000000); // fixed supply low floating_calldata.append(0); // fixed supply high floating_calldata.append(gov_addr.into()); floating_calldata.append(gov_addr.into()); - let (floating_addr, _) = floating_class - .deploy_at( - @floating_calldata, - 0x71cc3fbda6eb62d60c57c84eb995338fcb74a31dfb58e64f88185d1ac8ae8b8.try_into().unwrap() - ) + let floating_addr: ContractAddress = + 0x051c4b1fe3bf6774b87ad0b15ef5d1472759076e42944fff9b9f641ff13e5bbe + .try_into() .unwrap(); let time_zero = get_block_timestamp(); @@ -331,11 +327,12 @@ fn test_unstake_airdrop_team_member_failing() { prank(CheatTarget::One(gov_addr), team_member_1, CheatSpan::TargetCalls(6)); // Upgrade governance - let prop_id_gov_upgrade = props.submit_proposal(gov_class.class_hash.into(), 1); + let prop_id_gov_upgrade = props + .submit_proposal(0x04bc8bc7c476c4fca95624809dab1f1aa718edb566184a9d6dfe54f65b32b507, 1); props.vote(prop_id_gov_upgrade, 1); // Upgrade veCarm token - let prop_id_vecarm_upgrade = props.submit_proposal(voting_class.class_hash.into(), 2); + let prop_id_vecarm_upgrade = props.submit_proposal(0x008e98fd1f76f0d6fca8b03292e1dd6c8a6c5362f5aa0fd1186592168e9ad692, 2); props.vote(prop_id_vecarm_upgrade, 1); // Propose Airdrop @@ -453,3 +450,4 @@ fn RANDOM_USER_3_AIRDROP_CALLDATA() -> Array { // } // ] + diff --git a/tests/upgrade.cairo b/tests/upgrade.cairo index 6f96cfa..aef2ef7 100644 --- a/tests/upgrade.cairo +++ b/tests/upgrade.cairo @@ -150,43 +150,48 @@ fn scenario_airdrop_staked_carm() { .try_into() .unwrap(); - let gov_class: ContractClass = declare("Governance").expect('unable to declare gov'); - let floating_class: ContractClass = declare("CRMToken").expect('unable to declare CRM'); - let voting_class: ContractClass = declare("VeCRM").expect('unable to declare voting'); + //let gov_class: ContractClass = declare("Governance").expect('unable to declare gov'); + //let floating_class: ContractClass = declare("CRMToken").expect('unable to declare CRM'); + //let voting_class: ContractClass = declare("VeCRM").expect('unable to declare voting'); let mut floating_calldata = ArrayTrait::new(); floating_calldata.append(10000000000000000000000000); // fixed supply low floating_calldata.append(0); // fixed supply high floating_calldata.append(gov_addr.into()); floating_calldata.append(gov_addr.into()); - let (floating_addr, _) = floating_class - .deploy_at( - @floating_calldata, - 0x71cc3fbda6eb62d60c57c84eb995338fcb74a31dfb58e64f88185d1ac8ae8b8.try_into().unwrap() - ) + let floating_addr: ContractAddress = + 0x051c4b1fe3bf6774b87ad0b15ef5d1472759076e42944fff9b9f641ff13e5bbe + .try_into() .unwrap(); println!("Floating addr: {:?}", floating_addr); let time_zero = get_block_timestamp(); - let user1: ContractAddress = 0x0011d341c6e841426448ff39aa443a6dbb428914e05ba2259463c18308b86233 + let user1: ContractAddress = + 0x0011d341c6e841426448ff39aa443a6dbb428914e05ba2259463c18308b86233 // team + .try_into() + .unwrap(); + let user2: ContractAddress = + 0x052df7acdfd3174241fa6bd5e1b7192cd133f8fc30a2a6ed99b0ddbfb5b22dcd // community .try_into() .unwrap(); - let user2: ContractAddress = 0x052df7acdfd3174241fa6bd5e1b7192cd133f8fc30a2a6ed99b0ddbfb5b22dcd + let user3: ContractAddress = + 0x0583a9d956d65628f806386ab5b12dccd74236a3c6b930ded9cf3c54efc722a1 // team .try_into() .unwrap(); - let user3: ContractAddress = 0x0583a9d956d65628f806386ab5b12dccd74236a3c6b930ded9cf3c54efc722a1 + let investor1: ContractAddress = + 0x056d761e1e5d1918dba05de02afdbd8de8da01a63147dce828c9b1fe9227077d .try_into() .unwrap(); let props = IProposalsDispatcher { contract_address: gov_addr }; prank(CheatTarget::One(gov_addr), user1, CheatSpan::TargetCalls(6)); - let prop_id_gov_upgrade = props.submit_proposal(gov_class.class_hash.into(), 1); - let prop_id_vecarm_upgrade = props.submit_proposal(voting_class.class_hash.into(), 2); + let prop_id_gov_upgrade = props.submit_proposal(0x04bc8bc7c476c4fca95624809dab1f1aa718edb566184a9d6dfe54f65b32b507, 1); + let prop_id_vecarm_upgrade = props.submit_proposal(0x008e98fd1f76f0d6fca8b03292e1dd6c8a6c5362f5aa0fd1186592168e9ad692, 2); props.vote(prop_id_gov_upgrade, 1); props.vote(prop_id_vecarm_upgrade, 1); let prop_id_airdrop = props .submit_proposal( - voting_class.class_hash.into(), 3 + 0x1337, 3 ); // simulate airdrop proposal, no merkle tree root yet props.vote(prop_id_airdrop, 1); prank(CheatTarget::One(gov_addr), user2, CheatSpan::TargetCalls(3));