Skip to content

Commit

Permalink
feat(starknet): ERC721 uri, bridge ownership and collection white list (
Browse files Browse the repository at this point in the history
#201)

* feat(starknet): add `set_base_uri` and `set_token_uri` in ERC721Bridgeable contract

* feat(starknet): use OZ two steps Ownable implementation for bridge contract

* feat(starknet): add function to retrieve white list

* feat(starknet): emit event when collection whitelist list is updated
  • Loading branch information
ptisserand authored Apr 29, 2024
1 parent cf1c689 commit dab4470
Show file tree
Hide file tree
Showing 5 changed files with 501 additions and 15 deletions.
117 changes: 106 additions & 11 deletions apps/blockchain/starknet/src/bridge.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod bridge {
use starknet::contract_address::ContractAddressZeroable;
use starknet::eth_address::EthAddressZeroable;

use openzeppelin::access::ownable::OwnableComponent;
use openzeppelin::access::ownable::interface::{
IOwnableDispatcher, IOwnableDispatcherTrait
};
Expand All @@ -28,6 +29,7 @@ mod bridge {
CollectionDeployedFromL1,
ReplacedClassHash,
BridgeEnabled,
CollectionWhiteListUpdated,
};

use starklane::request::{
Expand All @@ -49,10 +51,15 @@ mod bridge {

use poseidon::poseidon_hash_span;

component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);

#[abi(embed_v0)]
impl OwnableTwoStepMixinImpl = OwnableComponent::OwnableTwoStepMixinImpl<ContractState>;

impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;

#[storage]
struct Storage {
// Bridge administrator.
bridge_admin: ContractAddress,
// Bridge address on L1 (to allow it to consume messages).
bridge_l1_address: EthAddress,
// The class to deploy for ERC721 tokens.
Expand All @@ -67,11 +74,16 @@ mod bridge {

// White list enabled flag
white_list_enabled: bool,

// Registry of whitelisted collections
white_list: LegacyMap::<ContractAddress, bool>,

white_listed_list: LegacyMap::<ContractAddress, (bool, ContractAddress)>,
white_listed_head: ContractAddress,

// Bridge enabled flag
enabled: bool,

#[substorage(v0)]
ownable: OwnableComponent::Storage,
}

#[constructor]
Expand All @@ -81,8 +93,7 @@ mod bridge {
bridge_l1_address: EthAddress,
erc721_bridgeable_class: ClassHash,
) {
self.bridge_admin.write(bridge_admin);

self.ownable.initializer(bridge_admin);
// TODO: add validation of inputs.
self.bridge_l1_address.write(bridge_l1_address);
self.erc721_bridgeable_class.write(erc721_bridgeable_class);
Expand All @@ -98,6 +109,9 @@ mod bridge {
WithdrawRequestCompleted: WithdrawRequestCompleted,
ReplacedClassHash: ReplacedClassHash,
BridgeEnabled: BridgeEnabled,
CollectionWhiteListUpdated: CollectionWhiteListUpdated,
#[flat]
OwnableEvent: OwnableComponent::Event,
}


Expand Down Expand Up @@ -302,13 +316,35 @@ mod bridge {

fn white_list_collection(ref self: ContractState, collection: ContractAddress, enabled: bool) {
ensure_is_admin(@self);
self.white_list.write(collection, enabled);
_white_list_collection(ref self, collection, enabled);
self.emit(CollectionWhiteListUpdated {
collection,
enabled,
});
}

fn is_white_listed(self: @ContractState, collection: ContractAddress) -> bool {
_is_white_listed(self, collection)
}

fn get_white_listed_collections(self: @ContractState) -> Span<ContractAddress> {
let mut white_listed = array![];
let mut current = self.white_listed_head.read();
loop {
if current.is_zero() {
break;
}
let (enabled, next) = self.white_listed_list.read(current);
if !enabled {
break;
} else {
white_listed.append(current);
current = next;
}
};
white_listed.span()
}

fn enable(ref self: ContractState, enable: bool) {
ensure_is_admin(@self);
self.enabled.write(enable);
Expand Down Expand Up @@ -348,7 +384,7 @@ mod bridge {

/// Ensures the caller is the bridge admin. Revert if it's not.
fn ensure_is_admin(self: @ContractState) {
assert(starknet::get_caller_address() == self.bridge_admin.read(), 'Unauthorized call');
self.ownable.assert_only_owner();
}

/// Ensures the bridge is enabled
Expand Down Expand Up @@ -432,17 +468,76 @@ mod bridge {
);

// update whitelist if needed
if self.white_list.read(l2_addr_from_deploy) != true {
self.white_list.write(l2_addr_from_deploy, true);
let (already_white_listed, _) = self.white_listed_list.read(l2_addr_from_deploy);
if already_white_listed != true {
_white_list_collection(ref self, l2_addr_from_deploy, true);
self.emit(CollectionWhiteListUpdated {
collection: l2_addr_from_deploy,
enabled: true,
});
}
l2_addr_from_deploy
}

fn _is_white_listed(self: @ContractState, collection: ContractAddress) -> bool {
let enabled = self.white_list_enabled.read();
if (enabled) {
return self.white_list.read(collection);
let (ret, _) = self.white_listed_list.read(collection);
return ret;
}
true
}

fn _white_list_collection(ref self: ContractState, collection: ContractAddress, enabled: bool) {
let no_value = starknet::contract_address_const::<0>();
let (current, _) = self.white_listed_list.read(collection);
if current != enabled {
let mut prev = self.white_listed_head.read();
if enabled {
self.white_listed_list.write(collection, (enabled, no_value));
if prev.is_zero() {
self.white_listed_head.write(collection);
return;
}
// find last element
loop {
let (_, next) = self.white_listed_list.read(prev);
if next.is_zero() {
break;
}
let (active, _) = self.white_listed_list.read(next);
if !active {
break;
}
prev = next;
};
self.white_listed_list.write(prev, (true, collection));
} else {
// change head
if prev == collection {
let (_, next) = self.white_listed_list.read(prev);
self.white_listed_list.write(collection, (false, no_value));
self.white_listed_head.write(next);
return;
}
// removed element from linked list
loop {
let (active, next) = self.white_listed_list.read(prev);
if next.is_zero() {
// end of list
break;
}
if !active {
break;
}
if next == collection {
let (_, target) = self.white_listed_list.read(collection);
self.white_listed_list.write(prev, (active, target));
break;
}
};
self.white_listed_list.write(collection, (false, no_value));
}
}
}
}
10 changes: 9 additions & 1 deletion apps/blockchain/starknet/src/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ trait IStarklane<T> {
fn is_white_list_enabled(self: @T) -> bool;
fn white_list_collection(ref self: T, collection: ContractAddress, enabled: bool);
fn is_white_listed(self: @T, collection: ContractAddress) -> bool;
fn get_white_listed_collections(self: @T) -> Span<ContractAddress>;

fn enable(ref self: T, enable: bool);
fn is_enabled(self: @T) -> bool;
Expand Down Expand Up @@ -98,4 +99,11 @@ struct L1L2CollectionMappingUpdated {
collection_l1: EthAddress,
#[key]
collection_l2: ContractAddress
}
}

#[derive(Drop, starknet::Event)]
struct CollectionWhiteListUpdated {
#[key]
collection: ContractAddress,
enabled: bool,
}
Loading

0 comments on commit dab4470

Please sign in to comment.