Skip to content

Commit

Permalink
feat(starknet): add support for maintenance mode
Browse files Browse the repository at this point in the history
  • Loading branch information
ptisserand committed Aug 22, 2024
1 parent 5e0eb16 commit efca109
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 46 deletions.
98 changes: 56 additions & 42 deletions contracts/ark_starknet/src/executor.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ mod executor {
use ark_oz::erc2981::{IERC2981Dispatcher, IERC2981DispatcherTrait};
use ark_oz::erc2981::{FeesRatio, FeesRatioDefault, FeesImpl};

use ark_starknet::interfaces::{IExecutor, IUpgradable};
use ark_starknet::interfaces::{IExecutor, IUpgradable, IMaintenance};

use ark_starknet::appchain_messaging::{
IAppchainMessagingDispatcher, IAppchainMessagingDispatcherTrait,
Expand Down Expand Up @@ -104,13 +104,16 @@ mod executor {
default_receiver: ContractAddress,
default_fees: FeesRatio,
creator_fees: LegacyMap<ContractAddress, (ContractAddress, FeesRatio)>,
// maintenance mode
enabled: bool,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
OrderExecuted: OrderExecuted,
CollectionFallbackFees: CollectionFallbackFees,
ExecutorEnabled: ExecutorEnabled,
}

#[derive(Drop, starknet::Event)]
Expand All @@ -132,6 +135,17 @@ mod executor {
receiver: ContractAddress,
}

#[derive(Drop, starknet::Event)]
struct ExecutorEnabled {
enable: bool
}

mod Errors {
const NOT_ENABLED: felt252 = 'Executor not enabled';
const UNAUTHORIZED_ADMIN: felt252 = 'Unauthorized admin address';
const FEES_RATIO_INVALID: felt252 = 'Fees ratio is invalid';
}

#[constructor]
fn constructor(
ref self: ContractState,
Expand All @@ -147,13 +161,14 @@ mod executor {
self.ark_fees.write(Default::default());
self.default_receiver.write(admin_address);
self.default_fees.write(Default::default());
self.enabled.write(true); // enabled by default
}


#[abi(embed_v0)]
impl ExecutorImpl of IExecutor<ContractState> {
fn set_broker_fees(ref self: ContractState, fees_ratio: FeesRatio) {
assert(fees_ratio.is_valid(), 'Fees ratio is invalid');
assert(fees_ratio.is_valid(), Errors::FEES_RATIO_INVALID);
self.broker_fees.write(starknet::get_caller_address(), fees_ratio);
}

Expand All @@ -168,11 +183,8 @@ mod executor {
}

fn set_ark_fees(ref self: ContractState, fees_ratio: FeesRatio) {
assert(
starknet::get_caller_address() == self.admin_address.read(),
'Unauthorized admin address'
);
assert(fees_ratio.is_valid(), 'Fees ratio is invalid');
_ensure_admin(@self);
assert(fees_ratio.is_valid(), Errors::FEES_RATIO_INVALID);

self.ark_fees.write(fees_ratio);
}
Expand All @@ -188,11 +200,8 @@ mod executor {
fn set_default_creator_fees(
ref self: ContractState, receiver: ContractAddress, fees_ratio: FeesRatio
) {
assert(
starknet::get_caller_address() == self.admin_address.read(),
'Unauthorized admin address'
);
assert(fees_ratio.is_valid(), 'Fees ratio is invalid');
_ensure_admin(@self);
assert(fees_ratio.is_valid(), Errors::FEES_RATIO_INVALID);
self.default_receiver.write(receiver);
self.default_fees.write(fees_ratio);
}
Expand All @@ -214,11 +223,8 @@ mod executor {
receiver: ContractAddress,
fees_ratio: FeesRatio
) {
assert(
starknet::get_caller_address() == self.admin_address.read(),
'Unauthorized admin address'
);
assert(fees_ratio.is_valid(), 'Fees ratio is invalid');
_ensure_admin(@self);
assert(fees_ratio.is_valid(), Errors::FEES_RATIO_INVALID);
self.creator_fees.write(nft_address, (receiver, fees_ratio));
}

Expand All @@ -233,51 +239,37 @@ mod executor {
fn update_arkchain_orderbook_address(
ref self: ContractState, orderbook_address: ContractAddress
) {
assert(
starknet::get_caller_address() == self.admin_address.read(),
'Unauthorized admin address'
);
_ensure_admin(@self);

self.arkchain_orderbook_address.write(orderbook_address);
}

fn update_messaging_address(ref self: ContractState, msger_address: ContractAddress) {
assert(
starknet::get_caller_address() == self.admin_address.read(),
'Unauthorized admin address'
);
_ensure_admin(@self);

self.messaging_address.write(msger_address);
}

fn update_eth_address(ref self: ContractState, eth_address: ContractAddress) {
assert(
starknet::get_caller_address() == self.admin_address.read(),
'Unauthorized admin address'
);
_ensure_admin(@self);

self.eth_contract_address.write(eth_address);
}

fn update_orderbook_address(ref self: ContractState, orderbook_address: ContractAddress) {
assert(
starknet::get_caller_address() == self.admin_address.read(),
'Unauthorized admin address'
);
_ensure_admin(@self);

self.arkchain_orderbook_address.write(orderbook_address);
}

fn update_admin_address(ref self: ContractState, admin_address: ContractAddress) {
assert(
starknet::get_caller_address() == self.admin_address.read(),
'Unauthorized admin address'
);
_ensure_admin(@self);

self.admin_address.write(admin_address);
}

fn cancel_order(ref self: ContractState, cancelInfo: CancelInfo) {
_ensure_is_enabled(@self);
let messaging = IAppchainMessagingDispatcher {
contract_address: self.messaging_address.read()
};
Expand All @@ -296,6 +288,7 @@ mod executor {
}

fn create_order(ref self: ContractState, order: OrderV1) {
_ensure_is_enabled(@self);
let messaging = IAppchainMessagingDispatcher {
contract_address: self.messaging_address.read()
};
Expand All @@ -319,6 +312,7 @@ mod executor {
}

fn fulfill_order(ref self: ContractState, fulfillInfo: FulfillInfo) {
_ensure_is_enabled(@self);
let messaging = IAppchainMessagingDispatcher {
contract_address: self.messaging_address.read()
};
Expand All @@ -345,7 +339,7 @@ mod executor {
// );

// Check if execution_info.currency_contract_address is whitelisted

_ensure_is_enabled(@self);
assert(
execution_info.payment_currency_chain_id == self.chain_id.read(),
'Chain ID is not SN_MAIN'
Expand Down Expand Up @@ -476,10 +470,7 @@ mod executor {
#[abi(embed_v0)]
impl ExecutorUpgradeImpl of IUpgradable<ContractState> {
fn upgrade(ref self: ContractState, class_hash: ClassHash) {
assert(
starknet::get_caller_address() == self.admin_address.read(),
'Unauthorized replace class'
);
_ensure_admin(@self);

match starknet::replace_class_syscall(class_hash) {
Result::Ok(_) => (), // emit event
Expand All @@ -488,6 +479,19 @@ mod executor {
}
}

#[abi(embed_v0)]
impl ExecutorMaintenanceImpl of IMaintenance<ContractState> {
fn is_enabled(self: @ContractState) -> bool {
self.enabled.read()
}

fn enable(ref self: ContractState, enable: bool) {
_ensure_admin(@self);
self.enabled.write(enable);
self.emit(ExecutorEnabled { enable })
}
}

fn _verify_create_order(self: @ContractState, vinfo: @CreateOrderInfo) {
let order = vinfo.order;
let caller = starknet::get_caller_address();
Expand Down Expand Up @@ -772,4 +776,14 @@ mod executor {
let amount = fees_ratio.compute_amount(payment_amount);
(receiver, amount)
}

fn _ensure_admin(self: @ContractState) {
assert(
starknet::get_caller_address() == self.admin_address.read(), Errors::UNAUTHORIZED_ADMIN
);
}

fn _ensure_is_enabled(self: @ContractState) {
assert(self.enabled.read(), Errors::NOT_ENABLED)
}
}
6 changes: 6 additions & 0 deletions contracts/ark_starknet/src/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ trait IExecutor<T> {
trait IUpgradable<T> {
fn upgrade(ref self: T, class_hash: ClassHash);
}

#[starknet::interface]
trait IMaintenance<T> {
fn is_enabled(self: @T) -> bool;
fn enable(ref self: T, enable: bool);
}
54 changes: 53 additions & 1 deletion contracts/ark_starknet/tests/integration/create_order.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use ark_common::protocol::order_v1::OrderV1;
use ark_common::protocol::order_types::RouteType;


use ark_starknet::interfaces::{IExecutorDispatcher, IExecutorDispatcherTrait,};
use ark_starknet::interfaces::{
IExecutorDispatcher, IExecutorDispatcherTrait, IMaintenanceDispatcher,
IMaintenanceDispatcherTrait
};

use ark_tokens::erc20::IFreeMintDispatcher as Erc20Dispatcher;
use ark_tokens::erc20::IFreeMintDispatcherTrait as Erc20DispatcherTrait;
Expand Down Expand Up @@ -110,3 +113,52 @@ fn test_create_order_offerer_not_own_ec721_token() {
IExecutorDispatcher { contract_address: executor_address }.create_order(order);
snf::stop_prank(CheatTarget::One(executor_address));
}

#[test]
#[should_panic(expected: ('Executor not enabled',))]
fn test_create_order_erc20_to_erc721_disabled() {
let (executor_address, erc20_address, nft_address) = setup();
let admin = contract_address_const::<'admin'>();
let offerer = contract_address_const::<'offerer'>();
let start_amount = 10_000_000;

Erc20Dispatcher { contract_address: erc20_address }.mint(offerer, start_amount);

let mut order = setup_order(erc20_address, nft_address);
order.offerer = offerer;
order.start_amount = start_amount;

snf::start_prank(CheatTarget::One(executor_address), admin);
IMaintenanceDispatcher { contract_address: executor_address }.enable(false);
snf::stop_prank(CheatTarget::One(executor_address));

snf::start_prank(CheatTarget::One(executor_address), offerer);
IExecutorDispatcher { contract_address: executor_address }.create_order(order);
snf::stop_prank(CheatTarget::One(executor_address));
}

#[test]
#[should_panic(expected: ('Executor not enabled',))]
fn test_create_order_erc721_to_erc20_disabled() {
let (executor_address, erc20_address, nft_address) = setup();
let admin = contract_address_const::<'admin'>();
let offerer = contract_address_const::<'offerer'>();

let token_id: u256 = Erc721Dispatcher { contract_address: nft_address }
.get_current_token_id()
.into();
Erc721Dispatcher { contract_address: nft_address }.mint(offerer, 'base_uri');

let mut order = setup_order(erc20_address, nft_address);
order.route = RouteType::Erc721ToErc20.into();
order.offerer = offerer;
order.token_id = Option::Some(token_id);

snf::start_prank(CheatTarget::One(executor_address), admin);
IMaintenanceDispatcher { contract_address: executor_address }.enable(false);
snf::stop_prank(CheatTarget::One(executor_address));

snf::start_prank(CheatTarget::One(executor_address), offerer);
IExecutorDispatcher { contract_address: executor_address }.create_order(order);
snf::stop_prank(CheatTarget::One(executor_address));
}
26 changes: 25 additions & 1 deletion contracts/ark_starknet/tests/integration/execute_order.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use ark_common::protocol::order_types::{FulfillInfo, ExecutionInfo, OrderTrait,

use ark_oz::erc2981::{IERC2981SetupDispatcher, IERC2981SetupDispatcherTrait};

use ark_starknet::interfaces::{IExecutorDispatcher, IExecutorDispatcherTrait, FeesRatio};
use ark_starknet::interfaces::{
IExecutorDispatcher, IExecutorDispatcherTrait, FeesRatio, IMaintenanceDispatcher,
IMaintenanceDispatcherTrait
};

use ark_tokens::erc20::{IFreeMintDispatcher, IFreeMintDispatcherTrait};
use ark_tokens::erc721::IFreeMintDispatcher as Erc721Dispatcher;
Expand Down Expand Up @@ -654,3 +657,24 @@ fn test_execute_order_check_fee_too_much_fees() {
assert_eq!(erc20.balance_of(fulfill_broker), 1_000_000, "Fulfill broker balance not correct");
assert_eq!(erc20.balance_of(listing_broker), 500_000, "Listing broker balance not correct");
}

#[test]
#[should_panic(expected: ('Executor not enabled',))]
fn test_execute_order_disabled() {
let fulfiller = contract_address_const::<'fulfiller'>();
let listing_broker = contract_address_const::<'listing_broker'>();
let fulfill_broker = contract_address_const::<'fulfill_broker'>();
let admin_address = contract_address_const::<'admin'>();
let offerer = contract_address_const::<'offerer'>();

let start_amount = 10_000_000;
let (executor_address, _erc20_address, _, execution_info) = setup_execute_order(
admin_address, offerer, fulfiller, listing_broker, fulfill_broker, start_amount, false
);

snf::start_prank(CheatTarget::One(executor_address), admin_address);
IMaintenanceDispatcher { contract_address: executor_address }.enable(false);
snf::stop_prank(CheatTarget::One(executor_address));

IExecutorDispatcher { contract_address: executor_address }.execute_order(execution_info);
}
40 changes: 39 additions & 1 deletion contracts/ark_starknet/tests/integration/fulfill_order.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use ark_common::protocol::order_v1::OrderV1;
use ark_common::protocol::order_types::{FulfillInfo, OrderTrait, RouteType};


use ark_starknet::interfaces::{IExecutorDispatcher, IExecutorDispatcherTrait,};
use ark_starknet::interfaces::{
IExecutorDispatcher, IExecutorDispatcherTrait, IMaintenanceDispatcher,
IMaintenanceDispatcherTrait
};

use ark_tokens::erc20::{IFreeMintDispatcher, IFreeMintDispatcherTrait};
use ark_tokens::erc721::IFreeMintDispatcher as Erc721Dispatcher;
Expand Down Expand Up @@ -503,3 +506,38 @@ fn test_fulfill_auction_order_fulfiller_same_as_offerer() {
IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info);
snf::stop_prank(CheatTarget::One(executor_address));
}

#[test]
#[should_panic(expected: ('Executor not enabled',))]
fn test_fulfill_order_not_enabled() {
let (executor_address, erc20_address, nft_address) = setup();
let admin = contract_address_const::<'admin'>();
let fulfiller = contract_address_const::<'fulfiller'>();

let token_id: u256 = Erc721Dispatcher { contract_address: nft_address }
.get_current_token_id()
.into();
Erc721Dispatcher { contract_address: nft_address }.mint(fulfiller, 'base_uri');
let (order_hash, offerer, start_amount) = create_offer_order(
executor_address, erc20_address, nft_address, token_id
);

snf::start_prank(CheatTarget::One(erc20_address), offerer);
IERC20Dispatcher { contract_address: erc20_address }.approve(executor_address, start_amount);
snf::stop_prank(CheatTarget::One(erc20_address));

let fulfill_info = create_fulfill_info(order_hash, fulfiller, nft_address, token_id);

snf::start_prank(CheatTarget::One(nft_address), fulfiller);
IERC721Dispatcher { contract_address: nft_address }
.set_approval_for_all(executor_address, true);
snf::stop_prank(CheatTarget::One(nft_address));

snf::start_prank(CheatTarget::One(executor_address), admin);
IMaintenanceDispatcher { contract_address: executor_address }.enable(false);
snf::stop_prank(CheatTarget::One(executor_address));

snf::start_prank(CheatTarget::One(executor_address), fulfiller);
IExecutorDispatcher { contract_address: executor_address }.fulfill_order(fulfill_info);
snf::stop_prank(CheatTarget::One(executor_address));
}
Loading

0 comments on commit efca109

Please sign in to comment.