diff --git a/demos/arcade-factory/README.md b/demos/arcade-factory/README.md index 9dfda522..18c110c1 100644 --- a/demos/arcade-factory/README.md +++ b/demos/arcade-factory/README.md @@ -2,9 +2,15 @@ This demo is a Cairo contract allowing [arcade account](https://github.com/BibliothecaDAO/arcade-account) deployment, funding and configuration in a single tx. +### Notes + +Usually an Arcade account is initialized with a `public key` and a `master account`, thus the master account will impact the account address. To avoid this, the Arcade factory deploys a custom arcade account for which the master account initialization is done outside of the constructor. + ### Hashes ``` factory: 0x0286bb21588bdb808fb52e1e27346413e1f5be82e7de6a173e34677c3531d958 account: 0x0251830adc3d8b4d818c2c309d71f1958308e8c745212480c26e01120c69ee49 ``` + +⚠️ This arcade account is not intended to be used in prod. diff --git a/demos/arcade-factory/src/factory/contract.cairo b/demos/arcade-factory/src/factory/contract.cairo index 0138ded4..a5bb0514 100644 --- a/demos/arcade-factory/src/factory/contract.cairo +++ b/demos/arcade-factory/src/factory/contract.cairo @@ -97,7 +97,8 @@ mod ArcadeFactory { // #[external(v0)] - fn set_arcade_account_implementation(ref self: ContractState, arcade_account_implementation: starknet::ClassHash) { + // method name cannot be too long :/ + fn set_arcade_account_impl(ref self: ContractState, arcade_account_implementation: starknet::ClassHash) { // only owner self.ownable.assert_only_owner(); diff --git a/demos/arcade-factory/src/factory/factory.cairo b/demos/arcade-factory/src/factory/factory.cairo index c3dcc537..43fedcab 100644 --- a/demos/arcade-factory/src/factory/factory.cairo +++ b/demos/arcade-factory/src/factory/factory.cairo @@ -2,6 +2,9 @@ mod FactoryComponent { use starknet::SyscallResultTrait; + use openzeppelin::utils::selectors; + use openzeppelin::introspection::interface::ISRC5_ID; + // locals use arcade_factory::factory::interface; @@ -13,8 +16,6 @@ mod FactoryComponent { const CONTRACT_ADDRESS_PREFIX: felt252 = 'STARKNET_CONTRACT_ADDRESS'; - const SUPPORTS_INTERFACE_SELECTOR: felt252 = 0xfe80f537b66d12a00b6d3c072b44afbb716e78dde5c3f0ef116ee93d3e3283; - // // Storage // @@ -113,14 +114,14 @@ mod FactoryComponent { ) { // check that the new implementation is a valid class hash // we cannot check if the implementation register to the arcade account interface - // but we can check if the implementation implements the supports_interface method + // but we can check if the implementation supports SRC5 let ret_data = starknet::library_call_syscall( class_hash: arcade_account_implementation, - function_selector: SUPPORTS_INTERFACE_SELECTOR, - calldata: array![ARCADE_ACCOUNT_ID].span() + function_selector: selectors::supports_interface, + calldata: array![ISRC5_ID].span() ).unwrap_syscall(); - assert(ret_data.len() == 1, 'Invalid arcade account impl'); + assert((ret_data.len() == 1) & (*ret_data.at(0) == true.into()), 'Invalid arcade account impl'); // update implementation self._arcade_account_implementation.write(arcade_account_implementation); diff --git a/demos/arcade-factory/src/factory/interface.cairo b/demos/arcade-factory/src/factory/interface.cairo index 0a61c0b1..64673a50 100644 --- a/demos/arcade-factory/src/factory/interface.cairo +++ b/demos/arcade-factory/src/factory/interface.cairo @@ -31,7 +31,7 @@ trait IFactory { trait ArcadeFactoryABI { // IFactory fn arcade_account_implementation(self: @TState) -> starknet::ClassHash; - fn set_arcade_account_implementation(ref self: TState, arcade_account_implementation: starknet::ClassHash); + fn set_arcade_account_impl(ref self: TState, arcade_account_implementation: starknet::ClassHash); fn compute_address( self: @TState, salt: felt252, diff --git a/demos/arcade-factory/src/tests/mocks/arcade_account_mock.cairo b/demos/arcade-factory/src/tests/mocks/arcade_account_mock.cairo index 9bad3b02..496f5fda 100644 --- a/demos/arcade-factory/src/tests/mocks/arcade_account_mock.cairo +++ b/demos/arcade-factory/src/tests/mocks/arcade_account_mock.cairo @@ -14,8 +14,6 @@ mod ArcadeAccountMock { // locals use super::ArcadeAccountMockABI; - use arcade_factory::account::interface; - component!(path: SRC5Component, storage: src5, event: SRC5Event); // Components @@ -78,6 +76,41 @@ mod ArcadeAccountMock { #[starknet::contract] mod ValidArcadeAccountMock { + use openzeppelin::introspection::src5::SRC5Component; + + // locals + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Components + + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // + // Events + // + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + SRC5Event: SRC5Component::Event, + } + + // + // Storage + // + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage, + } +} + +#[starknet::contract] +mod InvalidArcadeAccountMock { // locals use arcade_factory::account::interface; diff --git a/demos/arcade-factory/src/tests/test_factory.cairo b/demos/arcade-factory/src/tests/test_factory.cairo index 4202d885..99670ccd 100644 --- a/demos/arcade-factory/src/tests/test_factory.cairo +++ b/demos/arcade-factory/src/tests/test_factory.cairo @@ -10,6 +10,7 @@ use arcade_factory::factory::interface::{ ArcadeFactoryABIDispatcher, ArcadeFact use super::mocks::arcade_account_mock::{ ValidArcadeAccountMock, + InvalidArcadeAccountMock, ArcadeAccountMock, ArcadeAccountMockABIDispatcher, ArcadeAccountMockABIDispatcherTrait, @@ -45,7 +46,7 @@ fn setup() -> ArcadeFactoryABIDispatcher { #[available_gas(20000000)] // cannot have another error message from constructor #[should_panic(expected: ('Result::unwrap failed.',))] -fn test_constructor_invalid_arcade_account_implementation() { +fn test_constructor_unknown_arcade_account_implementation() { let owner = constants::OWNER(); utils::deploy( @@ -54,6 +55,19 @@ fn test_constructor_invalid_arcade_account_implementation() { ); } +#[test] +#[available_gas(20000000)] +// cannot have another error message from constructor +#[should_panic(expected: ('Result::unwrap failed.',))] +fn test_constructor_invalid_arcade_account_implementation() { + let owner = constants::OWNER(); + + utils::deploy( + contract_class_hash: ArcadeFactory::TEST_CLASS_HASH, + calldata: array![owner.into(), InvalidArcadeAccountMock::TEST_CLASS_HASH] + ); +} + // Arcade account implementation #[test] @@ -117,7 +131,7 @@ fn test_set_arcade_account_implementation() { let factory = setup(); testing::set_contract_address(constants::OWNER()); - factory.set_arcade_account_implementation( + factory.set_arcade_account_impl( arcade_account_implementation: ValidArcadeAccountMock::TEST_CLASS_HASH.try_into().unwrap() ); @@ -127,15 +141,27 @@ fn test_set_arcade_account_implementation() { ); } +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('Invalid arcade account impl', 'ENTRYPOINT_FAILED',))] +fn test_set_arcade_account_implementation_invalid() { + let factory = setup(); + + testing::set_contract_address(constants::OWNER()); + factory.set_arcade_account_impl( + arcade_account_implementation: InvalidArcadeAccountMock::TEST_CLASS_HASH.try_into().unwrap() + ); +} + #[test] #[available_gas(20000000)] // cannot have another error message until state revert during tx is supported #[should_panic(expected: ('CLASS_HASH_NOT_DECLARED', 'ENTRYPOINT_FAILED',))] -fn test_set_arcade_account_implementation_invalid() { +fn test_set_arcade_account_implementation_unknown() { let factory = setup(); testing::set_contract_address(constants::OWNER()); - factory.set_arcade_account_implementation(arcade_account_implementation: 'new class hash'.try_into().unwrap()); + factory.set_arcade_account_impl(arcade_account_implementation: 'new class hash'.try_into().unwrap()); } #[test] @@ -145,7 +171,7 @@ fn test_set_arcade_account_implementation_unauthorized() { let factory = setup(); testing::set_contract_address(constants::OTHER()); - factory.set_arcade_account_implementation(arcade_account_implementation: 'new class hash'.try_into().unwrap()); + factory.set_arcade_account_impl(arcade_account_implementation: 'new class hash'.try_into().unwrap()); } #[test] @@ -155,5 +181,5 @@ fn test_set_arcade_account_implementation_from_zero() { let factory = setup(); testing::set_contract_address(constants::ZERO()); - factory.set_arcade_account_implementation(arcade_account_implementation: 'new class hash'.try_into().unwrap()); + factory.set_arcade_account_impl(arcade_account_implementation: 'new class hash'.try_into().unwrap()); }