diff --git a/starknet/src/space/space.cairo b/starknet/src/space/space.cairo index 31c4c720..0e9ee514 100644 --- a/starknet/src/space/space.cairo +++ b/starknet/src/space/space.cairo @@ -75,6 +75,7 @@ trait ISpace { #[starknet::contract] mod Space { use super::ISpace; + use starknet::storage_access::{StorePacking, StoreUsingPacking}; use starknet::{ClassHash, ContractAddress, info, Store, syscalls}; use zeroable::Zeroable; use array::{ArrayTrait, SpanTrait}; @@ -90,8 +91,8 @@ mod Space { }; use sx::types::{ UserAddress, Choice, FinalizationStatus, Strategy, IndexedStrategy, Proposal, - IndexedStrategyTrait, IndexedStrategyImpl, UpdateSettingsCalldata, NoUpdateU32, - NoUpdateStrategy, NoUpdateArray + PackedProposal, IndexedStrategyTrait, IndexedStrategyImpl, UpdateSettingsCalldata, + NoUpdateU32, NoUpdateStrategy, NoUpdateArray }; use sx::utils::reinitializable::Reinitializable; use sx::utils::ReinitializableImpl; diff --git a/starknet/src/types.cairo b/starknet/src/types.cairo index 8c028ab2..a5662b4b 100644 --- a/starknet/src/types.cairo +++ b/starknet/src/types.cairo @@ -11,7 +11,7 @@ mod indexed_strategy; use indexed_strategy::{IndexedStrategy, IndexedStrategyImpl, IndexedStrategyTrait}; mod proposal; -use proposal::Proposal; +use proposal::{Proposal, PackedProposal}; mod proposal_status; use proposal_status::ProposalStatus; diff --git a/starknet/src/types/finalization_status.cairo b/starknet/src/types/finalization_status.cairo index 2c81657d..4fb51138 100644 --- a/starknet/src/types/finalization_status.cairo +++ b/starknet/src/types/finalization_status.cairo @@ -31,3 +31,23 @@ impl FinalizationStatusIntoU8 of Into { } } } + +impl FinalizationStatusIntoU128 of Into { + fn into(self: FinalizationStatus) -> u128 { + match self { + FinalizationStatus::Pending(_) => 0_u128, + FinalizationStatus::Executed(_) => 1_u128, + FinalizationStatus::Cancelled(_) => 2_u128, + } + } +} + +impl FinalizationStatusIntoU256 of Into { + fn into(self: FinalizationStatus) -> u256 { + match self { + FinalizationStatus::Pending(_) => 0_u256, + FinalizationStatus::Executed(_) => 1_u256, + FinalizationStatus::Cancelled(_) => 2_u256, + } + } +} diff --git a/starknet/src/types/proposal.cairo b/starknet/src/types/proposal.cairo index 4e8f196d..d6ce917c 100644 --- a/starknet/src/types/proposal.cairo +++ b/starknet/src/types/proposal.cairo @@ -1,16 +1,231 @@ +use sx::types::user_address::UserAddressTrait; use clone::Clone; use serde::Serde; use starknet::ContractAddress; +use starknet::storage_access::StorePacking; +use starknet::Store; +use sx::utils::math::pow; +use traits::{Into, TryInto}; use sx::types::{FinalizationStatus, UserAddress}; +use option::OptionTrait; +use debug::PrintTrait; -#[derive(Clone, Drop, Serde, PartialEq, starknet::Store)] +const BITMASK_32: u128 = 0xffffffff; +const BITMASK_64: u128 = 0xffffffffffffffff; +const BITMASK_128: u128 = 0xffffffffffffffffffffffffffffffff; + +const BITMASK_SECOND_U32: u128 = 0xffffffff00000000; +const BITMASK_THIRD_U32: u128 = 0xffffffff0000000000000000; +const BITMASK_FOURTH_U32: u128 = + 0xff000000000000000000000000; // Only 0xff because finalization_status is an u8 + +const TWO_POWER_32: u128 = 0x100000000; +const TWO_POWER_64: u128 = 0x10000000000000000; +const TWO_POWER_96: u128 = 0x1000000000000000000000000; + +#[derive(Clone, Drop, Serde, PartialEq)] struct Proposal { start_timestamp: u32, min_end_timestamp: u32, max_end_timestamp: u32, + finalization_status: FinalizationStatus, execution_payload_hash: felt252, execution_strategy: ContractAddress, author: UserAddress, - finalization_status: FinalizationStatus, active_voting_strategies: u256 } + +#[derive(Drop, starknet::Store)] +struct PackedProposal { + timestamps_and_finalization_status: u128, // In order: start, min, max, finalization_status + execution_payload_hash: felt252, + execution_strategy: ContractAddress, + author: UserAddress, + active_voting_strategies: u256, +} + +impl ProposalStorePacking of StorePacking { + fn pack(value: Proposal) -> PackedProposal { + let timestamps_and_finalization_status: u128 = (value.start_timestamp.into() + + value.min_end_timestamp.into() * TWO_POWER_32 + + value.max_end_timestamp.into() * TWO_POWER_64 + + value.finalization_status.into() * TWO_POWER_96); + PackedProposal { + timestamps_and_finalization_status, + execution_payload_hash: value.execution_payload_hash, + execution_strategy: value.execution_strategy, + author: value.author, + active_voting_strategies: value.active_voting_strategies, + } + } + + fn unpack(value: PackedProposal) -> Proposal { + let start_timestamp: u32 = (value.timestamps_and_finalization_status & BITMASK_32) + .try_into() + .unwrap(); + let min_end_timestamp: u32 = ((value.timestamps_and_finalization_status + & BITMASK_SECOND_U32) + / TWO_POWER_32) + .try_into() + .unwrap(); + let max_end_timestamp: u32 = ((value.timestamps_and_finalization_status & BITMASK_THIRD_U32) + / TWO_POWER_64) + .try_into() + .unwrap(); + let finalization_status: u8 = ((value.timestamps_and_finalization_status + & BITMASK_FOURTH_U32) + / TWO_POWER_96) + .try_into() + .unwrap(); + + let type_helper: Option = finalization_status + .try_into(); // For some reason, type couldn't be inferred... + let finalization_status: FinalizationStatus = type_helper.unwrap(); + + Proposal { + start_timestamp, + min_end_timestamp, + max_end_timestamp, + finalization_status, + execution_payload_hash: value.execution_payload_hash, + execution_strategy: value.execution_strategy, + author: value.author, + active_voting_strategies: value.active_voting_strategies, + } + } +} + +#[cfg(test)] +mod tests { + use super::{Proposal, PackedProposal, ProposalStorePacking}; + use super::FinalizationStatus; + use starknet::storage_access::StorePacking; + use starknet::contract_address_const; + use sx::types::{UserAddress}; + use clone::Clone; + + #[test] + fn test_pack_zero() { + let proposal = Proposal { + start_timestamp: 0, + min_end_timestamp: 0, + max_end_timestamp: 0, + finalization_status: FinalizationStatus::Pending(()), + execution_payload_hash: 0, + author: UserAddress::Starknet(contract_address_const::<0>()), + execution_strategy: contract_address_const::<0>(), + active_voting_strategies: 0_u256, + }; + + let packed = ProposalStorePacking::pack(proposal.clone()); + assert(packed.timestamps_and_finalization_status == 0, 'invalid zero packing'); + let result = ProposalStorePacking::unpack(packed); + assert(result == proposal, 'invalid zero unpacking'); + } + + + #[test] + fn test_pack_start_timestamp() { + let proposal = Proposal { + start_timestamp: 42, + min_end_timestamp: 0, + max_end_timestamp: 0, + finalization_status: FinalizationStatus::Pending(()), + execution_payload_hash: 0, + author: UserAddress::Starknet(contract_address_const::<0>()), + execution_strategy: contract_address_const::<0>(), + active_voting_strategies: 0_u256, + }; + + let packed = ProposalStorePacking::pack(proposal.clone()); + assert(packed.timestamps_and_finalization_status == 42, 'invalid start packing'); + let result = ProposalStorePacking::unpack(packed); + assert(result == proposal, 'invalid start unpacking'); + } + + #[test] + fn test_pack_min_timestamp() { + let proposal = Proposal { + start_timestamp: 0, + min_end_timestamp: 42, + max_end_timestamp: 0, + finalization_status: FinalizationStatus::Pending(()), + execution_payload_hash: 0, + author: UserAddress::Starknet(contract_address_const::<0>()), + execution_strategy: contract_address_const::<0>(), + active_voting_strategies: 0_u256, + }; + + let packed = ProposalStorePacking::pack(proposal.clone()); + assert(packed.timestamps_and_finalization_status == 0x2a00000000, 'invalid min packing'); + let result = ProposalStorePacking::unpack(packed); + assert(result == proposal, 'invalid min unpacking'); + } + + + #[test] + fn test_pack_max_timestamp() { + let proposal = Proposal { + start_timestamp: 0, + min_end_timestamp: 0, + max_end_timestamp: 42, + finalization_status: FinalizationStatus::Pending(()), + execution_payload_hash: 0, + author: UserAddress::Starknet(contract_address_const::<0>()), + execution_strategy: contract_address_const::<0>(), + active_voting_strategies: 0_u256, + }; + + let packed = ProposalStorePacking::pack(proposal.clone()); + assert( + packed.timestamps_and_finalization_status == 0x2a0000000000000000, 'invalid max packing' + ); + let result = ProposalStorePacking::unpack(packed); + assert(result == proposal, 'invalid max unpacking'); + } + + #[test] + fn test_pack_finalization_status() { + let proposal = Proposal { + start_timestamp: 0, + min_end_timestamp: 0, + max_end_timestamp: 0, + finalization_status: FinalizationStatus::Executed(()), + execution_payload_hash: 0, + author: UserAddress::Starknet(contract_address_const::<0>()), + execution_strategy: contract_address_const::<0>(), + active_voting_strategies: 0_u256, + }; + + let packed = ProposalStorePacking::pack(proposal.clone()); + assert( + packed.timestamps_and_finalization_status == 0x01000000000000000000000000, + 'invalid status packing' + ); + let result = ProposalStorePacking::unpack(packed); + assert(result == proposal, 'invalid status unpacking'); + } + + #[test] + fn test_pack_full() { + let proposal = Proposal { + start_timestamp: 0xffffffff, + min_end_timestamp: 0xffffffff, + max_end_timestamp: 0xffffffff, + finalization_status: FinalizationStatus::Cancelled(()), + execution_payload_hash: 0, + author: UserAddress::Starknet(contract_address_const::<0>()), + execution_strategy: contract_address_const::<0>(), + active_voting_strategies: 0_u256, + }; + + let packed = ProposalStorePacking::pack(proposal.clone()); + assert( + packed.timestamps_and_finalization_status == 0x02ffffffffffffffffffffffff, + 'invalid full packing' + ); + let result = ProposalStorePacking::unpack(packed); + assert(result == proposal, 'invalid full unpacking'); + } +} +