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: allow multiple operators #16

Merged
merged 8 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions src/config/component.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ mod config_cpt {

#[storage]
struct Storage {
/// Appchain operator that is allowed to update the state.
// TODO(#9): Multiple Operators
operator: ContractAddress,
/// Appchain operators that are allowed to update the state.
operators: LegacyMap<ContractAddress, bool>,
/// Program info (StarknetOS), with program hash and config hash.
program_info: (felt252, felt252),
/// Facts registry contract address.
Expand All @@ -38,13 +37,13 @@ mod config_cpt {
+HasComponent<TContractState>,
impl Ownable: ownable_cpt::HasComponent<TContractState>
> of IConfig<ComponentState<TContractState>> {
fn set_operator(ref self: ComponentState<TContractState>, address: ContractAddress) {
fn register_operator(ref self: ComponentState<TContractState>, address: ContractAddress) {
get_dep_component!(@self, Ownable).assert_only_owner();
self.operator.write(address)
self.operators.write(address, true);
}

fn get_operator(self: @ComponentState<TContractState>) -> ContractAddress {
self.operator.read()
fn is_operator(self: @ComponentState<TContractState>, address: ContractAddress) -> bool {
self.operators.read(address)
}

fn set_program_info(
Expand Down Expand Up @@ -93,7 +92,7 @@ mod config_cpt {
ref self: ComponentState<TContractState>, address: ContractAddress
) -> bool {
let owner = get_dep_component!(@self, Ownable).owner();
address == owner || address == self.operator.read()
address == owner || self.operators.read(address)
TAdev0 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
10 changes: 5 additions & 5 deletions src/config/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ use starknet::ContractAddress;
#[starknet::interface]
trait IConfig<T> {
/// Sets the operator that is in charge to push state updates.
///
/// Multiple operators can be set.
/// # Arguments
///
/// * `address` - The operator account address.
fn set_operator(ref self: T, address: ContractAddress);
fn register_operator(ref self: T, address: ContractAddress);
TAdev0 marked this conversation as resolved.
Show resolved Hide resolved

/// Gets the operator address.
/// Gets an operator address.
TAdev0 marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Returns
///
/// The operator's address.
fn get_operator(self: @T) -> ContractAddress;
/// True if the address is an operator.
TAdev0 marked this conversation as resolved.
Show resolved Hide resolved
fn is_operator(self: @T, address: ContractAddress) -> bool;
TAdev0 marked this conversation as resolved.
Show resolved Hide resolved

/// Sets the information of the program that generates the
/// state transition trace (namely StarknetOS).
Expand Down
31 changes: 23 additions & 8 deletions src/config/tests/test_config.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,37 @@ fn deploy_mock() -> IConfigDispatcher {
#[test]
fn config_set_operator_ok() {
let mock = deploy_mock();
assert(mock.get_operator() == c::ZERO(), 'expect 0 addr');
assert(!mock.is_operator(c::OPERATOR()), 'expect 0 addr');
TAdev0 marked this conversation as resolved.
Show resolved Hide resolved

snf::start_prank(CheatTarget::One(mock.contract_address), c::OWNER());

mock.set_operator(c::OPERATOR());
assert(mock.get_operator() == c::OPERATOR(), 'expect operator');
mock.register_operator(c::OPERATOR());
assert(mock.is_operator(c::OPERATOR()), 'expect operator');
}

#[test]
fn config_set_multiple_operators_ok() {
let mock = deploy_mock();
assert(!mock.is_operator(c::OPERATOR()), 'expect 0 addr');
assert(!mock.is_operator(c::OTHER()), 'expect 0 addr');
TAdev0 marked this conversation as resolved.
Show resolved Hide resolved

snf::start_prank(CheatTarget::One(mock.contract_address), c::OWNER());

mock.register_operator(c::OPERATOR());
mock.register_operator(c::OTHER());

assert(mock.is_operator(c::OPERATOR()), 'expect operator');
assert(mock.is_operator(c::OTHER()), 'expect operator');
}

#[test]
#[should_panic(expected: ('Caller is not the owner',))]
fn config_set_operator_unauthorized() {
let mock = deploy_mock();
assert(mock.get_operator() == c::ZERO(), 'expect 0 addr');
assert(!mock.is_operator(c::OPERATOR()), 'expect 0 addr');

mock.set_operator(c::OPERATOR());
assert(mock.get_operator() == c::OPERATOR(), 'expect operator');
mock.register_operator(c::OPERATOR());
assert(mock.is_operator(c::OPERATOR()), 'expect operator');
}

#[test]
Expand All @@ -45,7 +60,7 @@ fn config_set_program_info_ok() {
mock.set_program_info(0x1, 0x2);
assert(mock.get_program_info() == (0x1, 0x2), 'expect correct hashes');

mock.set_operator(c::OPERATOR());
mock.register_operator(c::OPERATOR());

// Operator can also set the program info.
snf::start_prank(CheatTarget::One(mock.contract_address), c::OPERATOR());
Expand Down Expand Up @@ -75,7 +90,7 @@ fn config_set_facts_registry_ok() {
mock.set_facts_registry(facts_registry_address);
assert(mock.get_facts_registry() == facts_registry_address, 'expect valid address');

mock.set_operator(c::OPERATOR());
mock.register_operator(c::OPERATOR());

// Operator can also set the program info.
snf::start_prank(CheatTarget::One(mock.contract_address), c::OPERATOR());
Expand Down