Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add factory and upgrade method #462

Merged
merged 10 commits into from
Aug 1, 2023
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
3 changes: 3 additions & 0 deletions starknet/src/tests.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod test_space;
mod test_factory;
mod test_upgrade;

mod mocks;
mod setup;
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::utils::types::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