diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 299b912..ace55bb 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -1,5 +1,5 @@ use crate::{bitcoin::block::BlockHeader, utils::digest::DigestStore}; -use starknet::get_block_timestamp; +use starknet::{ContractAddress, get_block_timestamp}; use utils::hash::Digest; @@ -33,6 +33,21 @@ pub impl BlockStatusImpl of BlockStatusTrait { #[starknet::interface] pub trait IUtuRelay { + /// Initializes the contract with an owner. + /// + /// This function is used instead of a constructor to ensure the contract address is fully + /// deterministic. When using a constructor, the constructor arguments become part of the + /// contract's address calculation. By moving initialization to a separate function that can + /// only be called once, we keep the address calculation dependent only on the contract's code + /// and class hash. + /// + /// # Arguments + /// * `owner` - The address that will be set as the contract owner + /// + /// # Reverts + /// * If the contract has already been initialized + fn initialize(ref self: TContractState, owner: ContractAddress); + /// Registers new blocks with the relay. /// /// This function allows anyone to register blocks (they don't have to be contiguous or in diff --git a/src/tests/utils.cairo b/src/tests/utils.cairo index 7e881d8..567032b 100644 --- a/src/tests/utils.cairo +++ b/src/tests/utils.cairo @@ -22,12 +22,14 @@ pub impl DigestIntoSpan of Into> { pub fn deploy_utu() -> IUtuRelayDispatcher { let contract = declare("UtuRelay").unwrap().contract_class(); - let _owner: ContractAddress = contract_address_const::<'owner'>(); - let mut constructor_calldata = array![]; - let (contract_address, _constructor_returned_data) = contract - .deploy(@constructor_calldata) - .unwrap(); + // Deploy with empty constructor + let (contract_address, _) = contract.deploy(@ArrayTrait::new()).unwrap(); + let dispatcher = IUtuRelayDispatcher { contract_address }; - IUtuRelayDispatcher { contract_address } + // Initialize the contract with owner + let owner: ContractAddress = contract_address_const::<'owner'>(); + dispatcher.initialize(owner); + + dispatcher } diff --git a/src/utu_relay.cairo b/src/utu_relay.cairo index 311bf47..c268057 100644 --- a/src/utu_relay.cairo +++ b/src/utu_relay.cairo @@ -39,6 +39,8 @@ pub mod UtuRelay { blocks: Map, // This is a mapping of each chain height to a block from the strongest chain registered chain: Map, + // This allows to keep a clean constructor and thus make address deterministic + initialized: bool, // Components #[substorage(v0)] ownable: OwnableComponent::Storage, @@ -56,9 +58,7 @@ pub mod UtuRelay { } #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); - } + fn constructor(ref self: ContractState) {} #[abi(embed_v0)] impl UpgradeableImpl of IUpgradeable { @@ -73,6 +73,19 @@ pub mod UtuRelay { #[abi(embed_v0)] impl UtuRelayImpl of IUtuRelay { + fn initialize(ref self: ContractState, owner: ContractAddress) { + // Check if already initialized + if self.initialized.read() { + panic!("Contract is already initialized"); + } + + // Set initialized flag + self.initialized.write(true); + + // Initialize ownership + self.ownable.initializer(owner); + } + fn register_blocks(ref self: ContractState, mut blocks: Span) { loop { match blocks.pop_front() { @@ -94,7 +107,6 @@ pub mod UtuRelay { }; } - fn update_canonical_chain( ref self: ContractState, begin_height: u64,