Skip to content

Commit

Permalink
feat: add factory and upgrade method (#462)
Browse files Browse the repository at this point in the history
* feat: add factory

* feat: upgrade method and tests

* fix: remove useless setup in constructor test

* fix: set caller address and contract address

* refactor: rename executioner to executor

* chore: remove useless comment

* chore: remove dead code

* typo: execution instead of exection

* comment: execution strategy instead of space itself

* refactor: move StrategyImpl to tests folder
  • Loading branch information
pscott authored Aug 1, 2023
1 parent a5c1769 commit 4b7b29e
Show file tree
Hide file tree
Showing 14 changed files with 561 additions and 125 deletions.
2 changes: 2 additions & 0 deletions starknet/src/factory.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod factory;
use factory::Factory;
48 changes: 48 additions & 0 deletions starknet/src/factory/factory.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use starknet::ContractAddress;
use starknet::ClassHash;

#[starknet::interface]
trait IFactory<TContractState> {
fn deploy(
self: @TContractState,
class_hash: ClassHash,
contract_address_salt: felt252,
calldata: Span<felt252>
) -> ContractAddress;
}


#[starknet::contract]
mod Factory {
use super::IFactory;
use starknet::ContractAddress;
use starknet::contract_address_const;
use starknet::syscalls::deploy_syscall;
use starknet::ClassHash;
use result::ResultTrait;

#[event]
fn SpaceDeployed(class_hash: ClassHash, space_address: ContractAddress) {}

#[storage]
struct Storage {}

#[external(v0)]
impl Factory of IFactory<ContractState> {
fn deploy(
self: @ContractState,
class_hash: ClassHash,
contract_address_salt: felt252,
calldata: Span<felt252>
) -> ContractAddress {
let (space_address, _) = deploy_syscall(
class_hash, contract_address_salt, calldata, false
)
.unwrap();

SpaceDeployed(class_hash, space_address);

space_address
}
}
}
2 changes: 2 additions & 0 deletions starknet/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod space;

mod factory;

mod proposal_validation_strategies;

mod authenticators;
Expand Down
19 changes: 17 additions & 2 deletions starknet/src/space/space.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use core::traits::Destruct;
use starknet::ContractAddress;
use starknet::{ClassHash, ContractAddress};
use sx::utils::types::{Strategy, Proposal, IndexedStrategy, Choice, UpdateSettingsCalldata};

#[starknet::interface]
Expand Down Expand Up @@ -49,12 +49,13 @@ trait ISpace<TContractState> {
execution_strategy: Strategy
);
fn cancel_proposal(ref self: TContractState, proposal_id: u256);
fn upgrade(ref self: TContractState, class_hash: ClassHash);
}

#[starknet::contract]
mod Space {
use super::ISpace;
use starknet::{ContractAddress, info, Store};
use starknet::{ClassHash, ContractAddress, info, Store};
use zeroable::Zeroable;
use array::{ArrayTrait, SpanTrait};
use clone::Clone;
Expand Down Expand Up @@ -163,6 +164,9 @@ mod Space {
#[event]
fn VotingDelayUpdated(_new_voting_delay: u64) {}

#[event]
fn Upgraded(class_hash: ClassHash) {}

#[external(v0)]
impl Space of ISpace<ContractState> {
fn propose(
Expand Down Expand Up @@ -318,6 +322,17 @@ mod Space {
ProposalCancelled(proposal_id);
}

fn upgrade(ref self: ContractState, class_hash: ClassHash) {
let state: Ownable::ContractState = Ownable::unsafe_new_contract_state();
Ownable::assert_only_owner(@state);

assert(
class_hash.is_non_zero(), 'Class Hash cannot be zero'
); // TODO: not sure this is needed
starknet::replace_class_syscall(class_hash).unwrap_syscall();
Upgraded(class_hash);
}

fn owner(self: @ContractState) -> ContractAddress {
//TODO: temporary component syntax
let state: Ownable::ContractState = Ownable::unsafe_new_contract_state();
Expand Down
5 changes: 5 additions & 0 deletions starknet/src/tests.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
mod test_space;
mod test_factory;
mod test_upgrade;

mod mocks;
mod setup;

mod utils;
1 change: 1 addition & 0 deletions starknet/src/tests/mocks.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod proposal_validation_always_fail;
mod executor;
41 changes: 41 additions & 0 deletions starknet/src/tests/mocks/executor.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#[starknet::contract]
mod ExecutorExecutionStrategy {
use sx::interfaces::IExecutionStrategy;
use sx::utils::types::{Proposal, ProposalStatus};
use sx::execution_strategies::simple_quorum::SimpleQuorumExecutionStrategy;
use starknet::ContractAddress;
use core::serde::Serde;
use core::array::ArrayTrait;
use option::OptionTrait;
use starknet::syscalls::call_contract_syscall;

#[storage]
struct Storage {}

#[derive(Drop, Serde)]
struct Transaction {
target: ContractAddress,
selector: felt252,
data: Array<felt252>,
}

#[external(v0)]
impl ExecutorExecutionStrategy of IExecutionStrategy<ContractState> {
// Dummy function that will just execute the `Transaction` in the payload, without needing any quorum.
fn execute(
ref self: ContractState,
proposal: Proposal,
votes_for: u256,
votes_against: u256,
votes_abstain: u256,
payload: Array<felt252>
) {
let mut sp4n = payload.span();
let tx: Transaction = Serde::<Transaction>::deserialize(ref sp4n).unwrap();
call_contract_syscall(tx.target, tx.selector, tx.data.span()).unwrap_syscall();
}
}

#[constructor]
fn constructor(ref self: ContractState) {}
}
1 change: 1 addition & 0 deletions starknet/src/tests/setup.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod setup;
160 changes: 160 additions & 0 deletions starknet/src/tests/setup/setup.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#[cfg(test)]
mod setup {
use array::ArrayTrait;
use starknet::{ContractAddress, contract_address_const};
use traits::{Into, TryInto};
use serde::{Serde};
use result::ResultTrait;
use option::OptionTrait;
use sx::utils::types::Strategy;
use sx::authenticators::vanilla::{VanillaAuthenticator};
use sx::execution_strategies::vanilla::VanillaExecutionStrategy;
use sx::voting_strategies::vanilla::VanillaVotingStrategy;
use sx::proposal_validation_strategies::vanilla::VanillaProposalValidationStrategy;
use sx::tests::utils::strategy_trait::StrategyImpl;
use integer::u256_from_felt252;
use starknet::testing;
use starknet::syscalls::deploy_syscall;
use sx::factory::factory::{Factory, IFactoryDispatcher, IFactoryDispatcherTrait};
use starknet::ClassHash;
use sx::space::space::{Space, ISpaceDispatcher, ISpaceDispatcherTrait};

#[derive(Drop)]
struct Config {
owner: ContractAddress,
min_voting_duration: u64,
max_voting_duration: u64,
voting_delay: u64,
proposal_validation_strategy: Strategy,
voting_strategies: Array<Strategy>,
authenticators: Array<ContractAddress>,
}

fn setup() -> Config {
let deployer = contract_address_const::<0x1234>();
testing::set_caller_address(deployer);
testing::set_contract_address(deployer);

// Space Settings
let owner = contract_address_const::<0x123456789>();
let max_voting_duration = 2_u64;
let min_voting_duration = 1_u64;
let voting_delay = 1_u64;
let quorum = u256_from_felt252(1);

// Deploy Vanilla Authenticator
let (vanilla_authenticator_address, _) = deploy_syscall(
VanillaAuthenticator::TEST_CLASS_HASH.try_into().unwrap(),
0,
array::ArrayTrait::<felt252>::new().span(),
false
)
.unwrap();
let mut authenticators = ArrayTrait::<ContractAddress>::new();
authenticators.append(vanilla_authenticator_address);

// Deploy Vanilla Proposal Validation Strategy
let (vanilla_proposal_validation_address, _) = deploy_syscall(
VanillaProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array::ArrayTrait::<felt252>::new().span(),
false
)
.unwrap();
let proposal_validation_strategy = StrategyImpl::from_address(
vanilla_proposal_validation_address
);

// Deploy Vanilla Voting Strategy
let (vanilla_voting_strategy_address, _) = deploy_syscall(
VanillaVotingStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array::ArrayTrait::<felt252>::new().span(),
false
)
.unwrap();
let mut voting_strategies = ArrayTrait::<Strategy>::new();
voting_strategies
.append(
Strategy {
address: vanilla_voting_strategy_address, params: ArrayTrait::<felt252>::new()
}
);

// Deploy Vanilla Execution Strategy
let mut constructor_calldata = ArrayTrait::<felt252>::new();
quorum.serialize(ref constructor_calldata);
let (vanilla_execution_strategy_address, _) = deploy_syscall(
VanillaExecutionStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
constructor_calldata.span(),
false
)
.unwrap();
let vanilla_execution_strategy = StrategyImpl::from_address(
vanilla_execution_strategy_address
);

Config {
owner,
min_voting_duration,
max_voting_duration,
voting_delay,
proposal_validation_strategy,
voting_strategies,
authenticators
}
}

fn get_constructor_calldata(
owner: @ContractAddress,
min_voting_duration: @u64,
max_voting_duration: @u64,
voting_delay: @u64,
proposal_validation_strategy: @Strategy,
voting_strategies: @Array<Strategy>,
authenticators: @Array<ContractAddress>
) -> Array<felt252> {
let mut constructor_calldata = array::ArrayTrait::<felt252>::new();
constructor_calldata.append((*owner).into());
constructor_calldata.append((*max_voting_duration).into());
constructor_calldata.append((*min_voting_duration).into());
constructor_calldata.append((*voting_delay).into());
proposal_validation_strategy.serialize(ref constructor_calldata);
voting_strategies.serialize(ref constructor_calldata);
authenticators.serialize(ref constructor_calldata);

constructor_calldata
}

fn deploy(config: @Config) -> (IFactoryDispatcher, ISpaceDispatcher) {
let space_class_hash: ClassHash = Space::TEST_CLASS_HASH.try_into().unwrap();
let contract_address_salt = 0;

let (factory_address, _) = deploy_syscall(
Factory::TEST_CLASS_HASH.try_into().unwrap(),
0,
ArrayTrait::<felt252>::new().span(),
false
)
.unwrap();

let factory = IFactoryDispatcher { contract_address: factory_address };

let constructor_calldata = get_constructor_calldata(
config.owner,
config.min_voting_duration,
config.max_voting_duration,
config.voting_delay,
config.proposal_validation_strategy,
config.voting_strategies,
config.authenticators
);
let space_address = factory
.deploy(space_class_hash, contract_address_salt, constructor_calldata.span());

let space = ISpaceDispatcher { contract_address: space_address };

(factory, space)
}
}
58 changes: 58 additions & 0 deletions starknet/src/tests/test_factory.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#[cfg(test)]
mod tests {
use array::ArrayTrait;
use starknet::{syscalls::deploy_syscall, testing, contract_address_const, };
use traits::TryInto;
use sx::factory::factory::{Factory, IFactoryDispatcher, IFactoryDispatcherTrait};
use option::OptionTrait;
use result::ResultTrait;
use sx::space::space::Space;
use sx::utils::types::Strategy;
use starknet::ClassHash;

use sx::tests::setup::setup::setup::{setup, get_constructor_calldata, deploy};

#[test]
#[available_gas(10000000000)]
fn test_deploy() {
// Deploy Space
let config = setup();

// TODO: check event gets emitted
deploy(@config);
}


#[test]
#[available_gas(10000000000)]
fn test_deploy_reuse_salt() {
let mut constructor_calldata = ArrayTrait::<felt252>::new();

let (factory_address, _) = deploy_syscall(
Factory::TEST_CLASS_HASH.try_into().unwrap(), 0, constructor_calldata.span(), false
)
.unwrap();

let factory = IFactoryDispatcher { contract_address: factory_address };

let space_class_hash: ClassHash = Space::TEST_CLASS_HASH.try_into().unwrap();
let contract_address_salt = 0;

let config = setup();
let constructor_calldata = get_constructor_calldata(
@config.owner,
@config.min_voting_duration,
@config.max_voting_duration,
@config.voting_delay,
@config.proposal_validation_strategy,
@config.voting_strategies,
@config.authenticators
);

let space_address = factory
.deploy(space_class_hash, contract_address_salt, constructor_calldata.span());
let space_address_2 = factory
.deploy(space_class_hash, contract_address_salt, constructor_calldata.span());
// TODO: this test should fail but doesn't fail currently because of how the test environment works
}
}
Loading

0 comments on commit 4b7b29e

Please sign in to comment.