From ede990f539fab8776b1fa6c3b35ed59c04d8217d Mon Sep 17 00:00:00 2001 From: Patrice Tisserand Date: Wed, 27 Mar 2024 03:07:28 +0100 Subject: [PATCH] starknet: add `collection_upgrade` to upgrade collection owned by bridge. --- apps/blockchain/starknet/src/bridge.cairo | 14 ++++- apps/blockchain/starknet/src/interfaces.cairo | 6 ++ .../starknet/src/tests/bridge_t.cairo | 60 ++++++++++++++++++- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/apps/blockchain/starknet/src/bridge.cairo b/apps/blockchain/starknet/src/bridge.cairo index fdbf6d71..a44e76d1 100644 --- a/apps/blockchain/starknet/src/bridge.cairo +++ b/apps/blockchain/starknet/src/bridge.cairo @@ -14,7 +14,7 @@ mod bridge { use starknet::contract_address::ContractAddressZeroable; use starknet::eth_address::EthAddressZeroable; - use starklane::interfaces::{IStarklane, IUpgradeable}; + use starklane::interfaces::{IStarklane, IUpgradeable, IUpgradeableDispatcher, IUpgradeableDispatcherTrait, IStarklaneCollectionAdmin}; // events use starklane::interfaces::{ DepositRequestInitiated, @@ -324,6 +324,18 @@ mod bridge { } } + #[abi(embed_v0)] + impl BridgeCollectionAdminImpl of IStarklaneCollectionAdmin { + fn collection_upgrade(ref self: ContractState, collection: ContractAddress, class_hash: ClassHash) { + assert( + starknet::get_caller_address() == self.bridge_admin.read(), + 'Unauthorized replace class' + ); + IUpgradeableDispatcher { contract_address: collection } + .upgrade(class_hash); + } + } + // *** INTERNALS *** /// Ensures the caller is the bridge admin. Revert if it's not. diff --git a/apps/blockchain/starknet/src/interfaces.cairo b/apps/blockchain/starknet/src/interfaces.cairo index 58b6b7fd..b7051e79 100644 --- a/apps/blockchain/starknet/src/interfaces.cairo +++ b/apps/blockchain/starknet/src/interfaces.cairo @@ -41,6 +41,12 @@ trait IUpgradeable { fn upgrade(ref self: T, class_hash: ClassHash); } +#[starknet::interface] +trait IStarklaneCollectionAdmin { + // try to upgrade the given collection with given class_hash + fn collection_upgrade(ref self: T, collection: ContractAddress, class_hash: ClassHash); +} + ////////////////////////// /// Events #[derive(Drop, starknet::Event)] diff --git a/apps/blockchain/starknet/src/tests/bridge_t.cairo b/apps/blockchain/starknet/src/tests/bridge_t.cairo index ce431a04..7ec62b6e 100644 --- a/apps/blockchain/starknet/src/tests/bridge_t.cairo +++ b/apps/blockchain/starknet/src/tests/bridge_t.cairo @@ -19,7 +19,10 @@ mod tests { use starknet::{ContractAddress, ClassHash, EthAddress}; use starklane::{ request::{Request, compute_request_hash}, - interfaces::{IStarklaneDispatcher, IStarklaneDispatcherTrait, IUpgradeableDispatcher, IUpgradeableDispatcherTrait}, + interfaces::{IStarklaneDispatcher, IStarklaneDispatcherTrait, + IUpgradeableDispatcher, IUpgradeableDispatcherTrait, + IStarklaneCollectionAdminDispatcher, IStarklaneCollectionAdminDispatcherTrait, + }, }; use starklane::token::{ interfaces::{ @@ -594,4 +597,59 @@ mod tests { stop_prank(CheatTarget::One(bridge_address)); } + #[test] + fn collection_upgrade_as_admin() { + // Need to declare here to get the class hash before deploy anything. + let erc721b_contract_class = declare("erc721_bridgeable"); + + let BRIDGE_ADMIN = starknet::contract_address_const::<'starklane'>(); + let BRIDGE_L1 = EthAddress { address: 'starklane_l1' }; + let OWNER_L2 = starknet::contract_address_const::<'owner_l2'>(); + + let bridge_address = deploy_starklane(BRIDGE_ADMIN, BRIDGE_L1, erc721b_contract_class.class_hash); + + let collection_l1: EthAddress = 0xe0c.try_into().unwrap(); + let collection_l2: ContractAddress = 0x0.try_into().unwrap(); + let owner_l1: EthAddress = 0xe00.try_into().unwrap(); + let owner_l2: ContractAddress = OWNER_L2.into(); + let ids: Span = array![0_u256, 1_u256].span(); + + let mut req = setup_request( + collection_l1, + collection_l2, + owner_l1, + owner_l2, + "name", + "symbol", + "base_uri" + ); + req.ids = ids; + let mut buf = array![]; + req.serialize(ref buf); + + let mut l1_handler = L1Handler { + contract_address: bridge_address, + // selector: 0x03593216f3a8b22f4cf375e5486e3d13bfde9d0f26976d20ac6f653c73f7e507, + function_selector: selector!("withdraw_auto_from_l1"), + from_address: BRIDGE_L1.into(), + payload: buf.span() + }; + let mut spy = spy_events(SpyOn::One(bridge_address)); + + l1_handler.execute().unwrap(); + let bridge = IStarklaneDispatcher { contract_address: bridge_address }; + + // Deserialize the request and check some expected values. + let mut sp = buf.span(); + let req = Serde::::deserialize(ref sp).unwrap(); + + let deployed_address = bridge.get_l2_collection_address(req.collection_l1.into()); + assert!(!deployed_address.is_zero(), "Expected deployed erc721"); + assert!(get_class_hash(deployed_address) == erc721b_contract_class.class_hash, "Expected class hash"); + + start_prank(CheatTarget::One(bridge_address), BRIDGE_ADMIN); + IStarklaneCollectionAdminDispatcher { contract_address: bridge_address}.collection_upgrade(deployed_address, get_class_hash(bridge_address)); + stop_prank(CheatTarget::One(bridge_address)); + assert!(get_class_hash(deployed_address) == get_class_hash(bridge_address), "Class hash not updated"); + } }