From 9bb2067ade9f043d8b6366947fa8bcbaa5d4e62d Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Tue, 16 Jan 2024 02:29:15 +0100 Subject: [PATCH 1/3] chore: add workflows, update scarb/snfoundry versions --- .github/workflows/build_contract.yml | 17 +++++++++++++++++ .github/workflows/test_contracts.yml | 16 ++++++++++++++++ .tool-versions | 2 ++ LICENSE | 21 +++++++++++++++++++++ README.md | 8 ++++---- Scarb.lock | 4 ++-- Scarb.toml | 4 ++-- 7 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/build_contract.yml create mode 100644 .github/workflows/test_contracts.yml create mode 100644 .tool-versions create mode 100644 LICENSE diff --git a/.github/workflows/build_contract.yml b/.github/workflows/build_contract.yml new file mode 100644 index 0000000..4bf1eec --- /dev/null +++ b/.github/workflows/build_contract.yml @@ -0,0 +1,17 @@ +name: Build + +on: [push, pull_request] + +permissions: read-all + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: software-mansion/setup-scarb@v1 + - name: Check cairo format + run: scarb fmt --check + working-directory: contracts + - name: Build cairo programs + run: scarb build \ No newline at end of file diff --git a/.github/workflows/test_contracts.yml b/.github/workflows/test_contracts.yml new file mode 100644 index 0000000..fef7988 --- /dev/null +++ b/.github/workflows/test_contracts.yml @@ -0,0 +1,16 @@ +name: Test + +on: [push, pull_request] +permissions: read-all + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: software-mansion/setup-scarb@v1 + - uses: foundry-rs/setup-snfoundry@v2 + with: + starknet-foundry-version: 0.12.0 + - name: Run cairo tests + run: snforge test \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..68a7d31 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +scarb 2.4.3 +starknet-foundry 0.14.0 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4a52f02 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Starknet Africa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 7d50a53..f034461 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # SRC-6551 Reference Implementation on Starknet -This repository contains the reference implementation of ERC-6551. +This repository contains the reference implementation of ERC-6551 on Starknet. -**NB:** This project is under active development and may undergo changes until the SRC-6551 SNIP is finalized. +**NB:** This project is under active development and may undergo changes until SNIP-72 is finalized. -## The SRC-6551 Standard -This proposal defines a system which assigns contract accounts to Non-fungible tokens (SRC-721). +## The Tokenbound Standard +This proposal defines a system which assigns contract accounts to Non-fungible tokens (ERC-721s). These accounts are referred to as token bound accounts and they allow NFTs to own assets and interact with applications, without requiring changes to existing smart contracts or infrastructure. diff --git a/Scarb.lock b/Scarb.lock index 546ebb0..74f4e18 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -3,8 +3,8 @@ version = 1 [[package]] name = "snforge_std" -version = "0.1.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.11.0#5465c41541c44a7804d16318fab45a2f0ccec9e7" +version = "0.14.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.14.0#e8cbecee4e31ed428c76d5173eaa90c8df796fe3" [[package]] name = "token_bound_accounts" diff --git a/Scarb.toml b/Scarb.toml index 46af7c0..a3d7cda 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -9,8 +9,8 @@ sierra = true casm = true [dependencies] -starknet = "2.3.1" -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.11.0" } +starknet = "2.4.3" +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.14.0" } [tool.snforge] # exit_first = true \ No newline at end of file From 31be1a2907d7de8adb28475d3fa71d8e33c423c4 Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Tue, 16 Jan 2024 02:34:02 +0100 Subject: [PATCH 2/3] chore: update workflows --- .github/workflows/build_contract.yml | 1 - .github/workflows/test_contracts.yml | 2 +- README.md | 2 +- src/registry/registry.cairo | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_contract.yml b/.github/workflows/build_contract.yml index 4bf1eec..ad2f8b1 100644 --- a/.github/workflows/build_contract.yml +++ b/.github/workflows/build_contract.yml @@ -12,6 +12,5 @@ jobs: - uses: software-mansion/setup-scarb@v1 - name: Check cairo format run: scarb fmt --check - working-directory: contracts - name: Build cairo programs run: scarb build \ No newline at end of file diff --git a/.github/workflows/test_contracts.yml b/.github/workflows/test_contracts.yml index fef7988..3ea7b99 100644 --- a/.github/workflows/test_contracts.yml +++ b/.github/workflows/test_contracts.yml @@ -11,6 +11,6 @@ jobs: - uses: software-mansion/setup-scarb@v1 - uses: foundry-rs/setup-snfoundry@v2 with: - starknet-foundry-version: 0.12.0 + starknet-foundry-version: 0.14.0 - name: Run cairo tests run: snforge test \ No newline at end of file diff --git a/README.md b/README.md index f034461..6af8e57 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SRC-6551 Reference Implementation on Starknet +# ERC-6551 Reference Implementation on Starknet This repository contains the reference implementation of ERC-6551 on Starknet. diff --git a/src/registry/registry.cairo b/src/registry/registry.cairo index 5e81000..851a536 100644 --- a/src/registry/registry.cairo +++ b/src/registry/registry.cairo @@ -4,7 +4,7 @@ #[starknet::contract] mod Registry { use core::result::ResultTrait; -use core::hash::HashStateTrait; + use core::hash::HashStateTrait; use starknet::{ContractAddress, get_caller_address, syscalls::call_contract_syscall, class_hash::ClassHash, class_hash::Felt252TryIntoClassHash, syscalls::deploy_syscall, SyscallResultTrait}; use zeroable::Zeroable; use traits::{Into, TryInto}; From ef1ed676329e782ecd08f56b0edc5282848981c1 Mon Sep 17 00:00:00 2001 From: Darlington02 Date: Tue, 16 Jan 2024 02:57:32 +0100 Subject: [PATCH 3/3] chore: update account contract events, run scarb fmt --- src/account.cairo | 2 +- src/account/account.cairo | 126 ++++++++++++++---------- src/interfaces.cairo | 2 +- src/interfaces/IAccount.cairo | 22 +++-- src/interfaces/IERC721.cairo | 16 ++- src/interfaces/IRegistry.cairo | 22 ++++- src/lib.cairo | 2 +- src/registry.cairo | 2 +- src/registry/registry.cairo | 54 ++++++---- src/test_helper.cairo | 2 +- src/test_helper/account_upgrade.cairo | 136 +++++++++++++++----------- src/test_helper/erc721_helper.cairo | 59 ++++++----- src/test_helper/hello_starknet.cairo | 6 +- tests/test_account.cairo | 59 ++++++----- tests/test_registry.cairo | 74 ++++++++++---- 15 files changed, 369 insertions(+), 215 deletions(-) diff --git a/src/account.cairo b/src/account.cairo index d1f6818..cfc38a2 100644 --- a/src/account.cairo +++ b/src/account.cairo @@ -1 +1 @@ -mod account; \ No newline at end of file +mod account; diff --git a/src/account/account.cairo b/src/account/account.cairo index 0258e11..8e2a0f5 100644 --- a/src/account/account.cairo +++ b/src/account/account.cairo @@ -3,7 +3,10 @@ //////////////////////////////// #[starknet::contract] mod Account { - use starknet::{get_tx_info, get_caller_address, get_contract_address, get_block_timestamp, ContractAddress, account::Call, call_contract_syscall, replace_class_syscall, ClassHash, SyscallResultTrait}; + use starknet::{ + get_tx_info, get_caller_address, get_contract_address, get_block_timestamp, ContractAddress, + account::Call, call_contract_syscall, replace_class_syscall, ClassHash, SyscallResultTrait + }; use ecdsa::check_ecdsa_signature; use array::{SpanTrait, ArrayTrait}; use box::BoxTrait; @@ -13,10 +16,11 @@ mod Account { use token_bound_accounts::interfaces::IAccount::IAccount; // SRC5 interface for token bound accounts - const TBA_INTERFACE_ID: felt252 = 0x539036932a2ab9c4734fbfd9872a1f7791a3f577e45477336ae0fd0a00c9ff; + const TBA_INTERFACE_ID: felt252 = + 0x539036932a2ab9c4734fbfd9872a1f7791a3f577e45477336ae0fd0a00c9ff; #[storage] - struct Storage{ + struct Storage { _token_contract: ContractAddress, // contract address of NFT _token_id: u256, // token ID of NFT _unlock_timestamp: u64, // time to unlock account when locked @@ -26,8 +30,9 @@ mod Account { #[derive(Drop, starknet::Event)] enum Event { AccountCreated: AccountCreated, - TransactionExecuted: TransactionExecuted, - Upgraded: Upgraded + AccountUpgraded: AccountUpgraded, + AccountLocked: AccountLocked, + TransactionExecuted: TransactionExecuted } /// @notice Emitted exactly once when the account is initialized @@ -49,16 +54,26 @@ mod Account { } /// @notice Emitted when the account upgrades to a new implementation - /// @param tokenContract the contract address of the NFT - /// @param tokenId the token ID of the NFT + /// @param account tokenbound account to be upgraded /// @param implementation the upgraded account class hash #[derive(Drop, starknet::Event)] - struct Upgraded { - tokenContract: ContractAddress, - tokenId: u256, + struct AccountUpgraded { + account: ContractAddress, implementation: ClassHash } + /// @notice Emitted when the account is locked + /// @param account tokenbound account who's lock function was triggered + /// @param locked_at timestamp at which the lock function was triggered + /// @param duration time duration for which the account remains locked + #[derive(Drop, starknet::Event)] + struct AccountLocked { + #[key] + account: ContractAddress, + locked_at: u64, + duration: u64, + } + #[constructor] fn constructor(ref self: ContractState, token_contract: ContractAddress, token_id: u256) { self._token_contract.write(token_contract); @@ -69,19 +84,19 @@ mod Account { } #[external(v0)] - impl IAccountImpl of IAccount{ + impl IAccountImpl of IAccount { /// @notice used for signature validation /// @param hash The message hash /// @param signature The signature to be validated - fn is_valid_signature(self: @ContractState, hash: felt252, signature: Span) -> felt252 { + fn is_valid_signature( + self: @ContractState, hash: felt252, signature: Span + ) -> felt252 { self._is_valid_signature(hash, signature) - } + } fn __validate_deploy__( - self: @ContractState, - class_hash:felt252, - contract_address_salt:felt252, - ) -> felt252{ + self: @ContractState, class_hash: felt252, contract_address_salt: felt252, + ) -> felt252 { self._validate_transaction() } @@ -91,13 +106,13 @@ mod Account { /// @notice validate an account transaction /// @param calls an array of transactions to be executed - fn __validate__( ref self: ContractState, mut calls:Array) -> felt252{ + fn __validate__(ref self: ContractState, mut calls: Array) -> felt252 { self._validate_transaction() } /// @notice executes a transaction /// @param calls an array of transactions to be executed - fn __execute__(ref self: ContractState, mut calls:Array) -> Array> { + fn __execute__(ref self: ContractState, mut calls: Array) -> Array> { self._assert_only_owner(); let (lock_status, _) = self._is_locked(); assert(!lock_status, 'Account: account is locked!'); @@ -115,14 +130,16 @@ mod Account { /// @notice gets the token bound NFT owner /// @param token_contract the contract address of the NFT /// @param token_id the token ID of the NFT - fn owner(self: @ContractState, token_contract:ContractAddress, token_id:u256) -> ContractAddress { - self._get_owner(token_contract, token_id) + fn owner( + self: @ContractState, token_contract: ContractAddress, token_id: u256 + ) -> ContractAddress { + self._get_owner(token_contract, token_id) } /// @notice returns the contract address and token ID of the NFT fn token(self: @ContractState) -> (ContractAddress, u256) { self._get_token() - } + } /// @notice ugprades an account implementation /// @param implementation the new class_hash @@ -132,12 +149,8 @@ mod Account { assert(!lock_status, 'Account: account is locked!'); assert(!implementation.is_zero(), 'Invalid class hash'); replace_class_syscall(implementation).unwrap_syscall(); - self.emit(Upgraded{ - tokenContract: self._token_contract.read(), - tokenId: self._token_id.read(), - implementation, - }); - } + self.emit(AccountUpgraded { account: get_contract_address(), implementation, }); + } // @notice protection mechanism for selling token bound accounts. can't execute when account is locked // @param duration for which to lock account @@ -148,7 +161,13 @@ mod Account { let current_timestamp = get_block_timestamp(); let unlock_time = current_timestamp + duration; self._unlock_timestamp.write(unlock_time); - } + self + .emit( + AccountLocked { + account: get_contract_address(), locked_at: current_timestamp, duration + } + ); + } // @notice returns account lock status and time left until account unlocks fn is_locked(self: @ContractState) -> (bool, u64) { @@ -158,18 +177,18 @@ mod Account { // @notice check that account supports TBA interface // @param interface_id interface to be checked against fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { - if(interface_id == TBA_INTERFACE_ID) { + if (interface_id == TBA_INTERFACE_ID) { return true; } else { return false; } - } + } } #[generate_trait] - impl internalImpl of InternalTrait{ + impl internalImpl of InternalTrait { /// @notice check that caller is the token bound account - fn _assert_only_owner(ref self: ContractState){ + fn _assert_only_owner(ref self: ContractState) { let caller = get_caller_address(); let owner = self._get_owner(self._token_contract.read(), self._token_id.read()); assert(caller == owner, 'Account: unathorized'); @@ -179,11 +198,15 @@ mod Account { /// @param token_contract contract address of NFT // @param token_id token ID of NFT // NB: This function aims for compatibility with all contracts (snake or camel case) but do not work as expected on mainnet as low level calls do not return err at the moment. Should work for contracts which implements CamelCase but not snake_case until starknet v0.15. - fn _get_owner(self: @ContractState, token_contract: ContractAddress, token_id: u256) -> ContractAddress { + fn _get_owner( + self: @ContractState, token_contract: ContractAddress, token_id: u256 + ) -> ContractAddress { let mut calldata: Array = ArrayTrait::new(); Serde::serialize(@token_id, ref calldata); - let mut res = call_contract_syscall(token_contract, selector!("ownerOf"), calldata.span()); - if(res.is_err()) { + let mut res = call_contract_syscall( + token_contract, selector!("ownerOf"), calldata.span() + ); + if (res.is_err()) { res = call_contract_syscall(token_contract, selector!("owner_of"), calldata.span()); } let mut address = res.unwrap(); @@ -201,12 +224,12 @@ mod Account { fn _is_locked(self: @ContractState) -> (bool, u64) { let unlock_timestamp = self._unlock_timestamp.read(); let current_time = get_block_timestamp(); - if(current_time < unlock_timestamp) { + if (current_time < unlock_timestamp) { let time_until_unlocks = unlock_timestamp - current_time; return (true, time_until_unlocks); } else { return (false, 0_u64); - } + } } /// @notice internal function for tx validation @@ -214,24 +237,29 @@ mod Account { let tx_info = get_tx_info().unbox(); let tx_hash = tx_info.transaction_hash; let signature = tx_info.signature; - assert(self._is_valid_signature(tx_hash, signature) == starknet::VALIDATED, 'Account: invalid signature'); + assert( + self._is_valid_signature(tx_hash, signature) == starknet::VALIDATED, + 'Account: invalid signature' + ); starknet::VALIDATED } /// @notice internal function for signature validation - fn _is_valid_signature(self: @ContractState, hash:felt252, signature: Span) -> felt252 { + fn _is_valid_signature( + self: @ContractState, hash: felt252, signature: Span + ) -> felt252 { let signature_length = signature.len(); assert(signature_length == 2_u32, 'Account: invalid sig length'); let caller = get_caller_address(); let owner = self._get_owner(self._token_contract.read(), self._token_id.read()); - if(caller == owner) { + if (caller == owner) { return starknet::VALIDATED; } else { return 0; } } - + /// @notice internal function for executing transactions /// @param calls An array of transactions to be executed fn _execute_calls(ref self: ContractState, mut calls: Span) -> Array> { @@ -241,21 +269,19 @@ mod Account { loop { match calls.pop_front() { Option::Some(call) => { - match call_contract_syscall(*call.to, *call.selector, call.calldata.span()) { - Result::Ok(mut retdata) => { - result.append(retdata); - }, + match call_contract_syscall( + *call.to, *call.selector, call.calldata.span() + ) { + Result::Ok(mut retdata) => { result.append(retdata); }, Result::Err(revert_reason) => { panic_with_felt252('multicall_failed'); } } }, - Option::None(_) => { - break(); - } + Option::None(_) => { break (); } }; }; result } } -} \ No newline at end of file +} diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 57de7a8..86a419e 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -1,3 +1,3 @@ mod IAccount; mod IERC721; -mod IRegistry; \ No newline at end of file +mod IRegistry; diff --git a/src/interfaces/IAccount.cairo b/src/interfaces/IAccount.cairo index 6c5891f..58a21cc 100644 --- a/src/interfaces/IAccount.cairo +++ b/src/interfaces/IAccount.cairo @@ -3,14 +3,20 @@ use starknet::ClassHash; use starknet::account::Call; #[starknet::interface] -trait IAccount{ - fn is_valid_signature(self: @TContractState, hash:felt252, signature: Span) -> felt252; - fn __validate__(ref self: TContractState, calls:Array) -> felt252; - fn __validate_declare__(self:@TContractState, class_hash:felt252) -> felt252; - fn __validate_deploy__(self: @TContractState, class_hash:felt252, contract_address_salt:felt252) -> felt252; - fn __execute__(ref self: TContractState, calls:Array) -> Array>; - fn token(self:@TContractState) -> (ContractAddress, u256); - fn owner(self: @TContractState, token_contract:ContractAddress, token_id:u256) -> ContractAddress; +trait IAccount { + fn is_valid_signature( + self: @TContractState, hash: felt252, signature: Span + ) -> felt252; + fn __validate__(ref self: TContractState, calls: Array) -> felt252; + fn __validate_declare__(self: @TContractState, class_hash: felt252) -> felt252; + fn __validate_deploy__( + self: @TContractState, class_hash: felt252, contract_address_salt: felt252 + ) -> felt252; + fn __execute__(ref self: TContractState, calls: Array) -> Array>; + fn token(self: @TContractState) -> (ContractAddress, u256); + fn owner( + self: @TContractState, token_contract: ContractAddress, token_id: u256 + ) -> ContractAddress; fn upgrade(ref self: TContractState, implementation: ClassHash); fn lock(ref self: TContractState, duration: u64); fn is_locked(self: @TContractState) -> (bool, u64); diff --git a/src/interfaces/IERC721.cairo b/src/interfaces/IERC721.cairo index a633437..387361a 100644 --- a/src/interfaces/IERC721.cairo +++ b/src/interfaces/IERC721.cairo @@ -5,16 +5,24 @@ trait IERC721 { fn balance_of(self: @TContractState, account: ContractAddress) -> u256; fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; fn ownerOf(self: @TContractState, token_id: u256) -> ContractAddress; - fn transfer_from(ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn transfer_from( + ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ); fn safe_transfer_from( - ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span + ref self: TContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span ); fn approve(ref self: TContractState, to: ContractAddress, token_id: u256); fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool); fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; - fn is_approved_for_all(self: @TContractState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn is_approved_for_all( + self: @TContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool; // IERC721Metadata fn name(self: @TContractState) -> felt252; fn symbol(self: @TContractState) -> felt252; fn token_uri(self: @TContractState, token_id: u256) -> felt252; -} \ No newline at end of file +} diff --git a/src/interfaces/IRegistry.cairo b/src/interfaces/IRegistry.cairo index dcaadf7..e847556 100644 --- a/src/interfaces/IRegistry.cairo +++ b/src/interfaces/IRegistry.cairo @@ -2,7 +2,21 @@ use starknet::ContractAddress; #[starknet::interface] trait IRegistry { - fn create_account(ref self: TContractState, implementation_hash: felt252, token_contract: ContractAddress, token_id: u256, salt: felt252) -> ContractAddress; - fn get_account(self: @TContractState, implementation_hash: felt252, token_contract: ContractAddress, token_id: u256, salt: felt252) -> ContractAddress; - fn total_deployed_accounts(self: @TContractState, token_contract: ContractAddress, token_id: u256) -> u8; -} \ No newline at end of file + fn create_account( + ref self: TContractState, + implementation_hash: felt252, + token_contract: ContractAddress, + token_id: u256, + salt: felt252 + ) -> ContractAddress; + fn get_account( + self: @TContractState, + implementation_hash: felt252, + token_contract: ContractAddress, + token_id: u256, + salt: felt252 + ) -> ContractAddress; + fn total_deployed_accounts( + self: @TContractState, token_contract: ContractAddress, token_id: u256 + ) -> u8; +} diff --git a/src/lib.cairo b/src/lib.cairo index 6747180..e35e00b 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,4 +1,4 @@ mod registry; mod account; mod interfaces; -mod test_helper; \ No newline at end of file +mod test_helper; diff --git a/src/registry.cairo b/src/registry.cairo index 70f78c6..516f5b2 100644 --- a/src/registry.cairo +++ b/src/registry.cairo @@ -1 +1 @@ -mod registry; \ No newline at end of file +mod registry; diff --git a/src/registry/registry.cairo b/src/registry/registry.cairo index 851a536..8b9b442 100644 --- a/src/registry/registry.cairo +++ b/src/registry/registry.cairo @@ -5,7 +5,10 @@ mod Registry { use core::result::ResultTrait; use core::hash::HashStateTrait; - use starknet::{ContractAddress, get_caller_address, syscalls::call_contract_syscall, class_hash::ClassHash, class_hash::Felt252TryIntoClassHash, syscalls::deploy_syscall, SyscallResultTrait}; + use starknet::{ + ContractAddress, get_caller_address, syscalls::call_contract_syscall, class_hash::ClassHash, + class_hash::Felt252TryIntoClassHash, syscalls::deploy_syscall, SyscallResultTrait + }; use zeroable::Zeroable; use traits::{Into, TryInto}; use option::OptionTrait; @@ -17,7 +20,9 @@ mod Registry { #[storage] struct Storage { - registry_deployed_accounts: LegacyMap<(ContractAddress, u256), u8>, // tracks no. of deployed accounts by registry for an NFT + registry_deployed_accounts: LegacyMap< + (ContractAddress, u256), u8 + >, // tracks no. of deployed accounts by registry for an NFT } #[event] @@ -50,26 +55,25 @@ mod Registry { token_contract: ContractAddress, token_id: u256, salt: felt252 - ) -> ContractAddress { - let owner = self._get_owner(token_contract, token_id); + ) -> ContractAddress { + let owner = self._get_owner(token_contract, token_id); assert(owner == get_caller_address(), 'CALLER_IS_NOT_OWNER'); - let mut constructor_calldata: Array = array![token_contract.into(), token_id.low.into(), token_id.high.into()]; + let mut constructor_calldata: Array = array![ + token_contract.into(), token_id.low.into(), token_id.high.into() + ]; let class_hash: ClassHash = implementation_hash.try_into().unwrap(); let result = deploy_syscall(class_hash, salt, constructor_calldata.span(), true); let (account_address, _) = result.unwrap_syscall(); - let new_deployment_index: u8 = self.registry_deployed_accounts.read((token_contract, token_id)) + 1_u8; + let new_deployment_index: u8 = self + .registry_deployed_accounts + .read((token_contract, token_id)) + + 1_u8; self.registry_deployed_accounts.write((token_contract, token_id), new_deployment_index); - self.emit( - AccountCreated { - account_address, - token_contract, - token_id, - } - ); + self.emit(AccountCreated { account_address, token_contract, token_id, }); account_address } @@ -79,7 +83,13 @@ mod Registry { /// @param token_contract the contract address of the NFT /// @param token_id the ID of the NFT /// @param salt random salt for deployment - fn get_account(self: @ContractState, implementation_hash: felt252, token_contract: ContractAddress, token_id: u256, salt: felt252) -> ContractAddress { + fn get_account( + self: @ContractState, + implementation_hash: felt252, + token_contract: ContractAddress, + token_id: u256, + salt: felt252 + ) -> ContractAddress { let constructor_calldata_hash = PedersenTrait::new(0) .update(token_contract.into()) .update(token_id.low.into()) @@ -103,7 +113,9 @@ mod Registry { /// @notice returns the total no. of deployed tokenbound accounts for an NFT by the registry /// @param token_contract the contract address of the NFT /// @param token_id the ID of the NFT - fn total_deployed_accounts(self: @ContractState, token_contract: ContractAddress, token_id: u256) -> u8 { + fn total_deployed_accounts( + self: @ContractState, token_contract: ContractAddress, token_id: u256 + ) -> u8 { self.registry_deployed_accounts.read((token_contract, token_id)) } } @@ -114,15 +126,19 @@ mod Registry { /// @param token_contract contract address of NFT // @param token_id token ID of NFT // NB: This function aims for compatibility with all contracts (snake or camel case) but do not work as expected on mainnet as low level calls do not return err at the moment. Should work for contracts which implements CamelCase but not snake_case until starknet v0.15. - fn _get_owner(self: @ContractState, token_contract: ContractAddress, token_id: u256) -> ContractAddress { + fn _get_owner( + self: @ContractState, token_contract: ContractAddress, token_id: u256 + ) -> ContractAddress { let mut calldata: Array = ArrayTrait::new(); Serde::serialize(@token_id, ref calldata); - let mut res = call_contract_syscall(token_contract, selector!("ownerOf"), calldata.span()); - if(res.is_err()) { + let mut res = call_contract_syscall( + token_contract, selector!("ownerOf"), calldata.span() + ); + if (res.is_err()) { res = call_contract_syscall(token_contract, selector!("owner_of"), calldata.span()); } let mut address = res.unwrap(); Serde::::deserialize(ref address).unwrap() } } -} \ No newline at end of file +} diff --git a/src/test_helper.cairo b/src/test_helper.cairo index 68a0bae..57f17fb 100644 --- a/src/test_helper.cairo +++ b/src/test_helper.cairo @@ -1,3 +1,3 @@ mod hello_starknet; mod account_upgrade; -mod erc721_helper; \ No newline at end of file +mod erc721_helper; diff --git a/src/test_helper/account_upgrade.cairo b/src/test_helper/account_upgrade.cairo index bd41e1c..a3e292c 100644 --- a/src/test_helper/account_upgrade.cairo +++ b/src/test_helper/account_upgrade.cairo @@ -2,16 +2,23 @@ use array::{ArrayTrait, SpanTrait}; use starknet::{account::Call, ContractAddress, ClassHash}; #[starknet::interface] -trait IUpgradedAccount{ +trait IUpgradedAccount { fn get_public_key(self: @TContractState) -> felt252; - fn set_public_key(ref self: TContractState, new_public_key:felt252); - fn isValidSignature(self: @TContractState, hash:felt252, signature: Span) -> bool; - fn __validate__(ref self: TContractState, calls:Array) -> felt252; - fn __validate_declare__(self:@TContractState, class_hash:felt252) -> felt252; - fn __validate_deploy__(self: @TContractState, class_hash:felt252, contract_address_salt:felt252, public_key:felt252) -> felt252; - fn __execute__(ref self: TContractState, calls:Array) -> Array>; - fn token(self:@TContractState) -> (ContractAddress, u256); - fn owner(ref self: TContractState, token_contract:ContractAddress, token_id:u256) -> ContractAddress; + fn set_public_key(ref self: TContractState, new_public_key: felt252); + fn isValidSignature(self: @TContractState, hash: felt252, signature: Span) -> bool; + fn __validate__(ref self: TContractState, calls: Array) -> felt252; + fn __validate_declare__(self: @TContractState, class_hash: felt252) -> felt252; + fn __validate_deploy__( + self: @TContractState, + class_hash: felt252, + contract_address_salt: felt252, + public_key: felt252 + ) -> felt252; + fn __execute__(ref self: TContractState, calls: Array) -> Array>; + fn token(self: @TContractState) -> (ContractAddress, u256); + fn owner( + ref self: TContractState, token_contract: ContractAddress, token_id: u256 + ) -> ContractAddress; fn upgrade(ref self: TContractState, implementation: ClassHash); fn version(self: @TContractState) -> u8; } @@ -20,14 +27,22 @@ trait IUpgradedAccount{ trait IERC721 { fn balance_of(self: @TContractState, account: ContractAddress) -> u256; fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; - fn transfer_from(ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn transfer_from( + ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ); fn safe_transfer_from( - ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span + ref self: TContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span ); fn approve(ref self: TContractState, to: ContractAddress, token_id: u256); fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool); fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; - fn is_approved_for_all(self: @TContractState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn is_approved_for_all( + self: @TContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool; // IERC721Metadata fn name(self: @TContractState) -> felt252; fn symbol(self: @TContractState) -> felt252; @@ -36,7 +51,10 @@ trait IERC721 { #[starknet::contract] mod UpgradedAccount { - use starknet::{get_tx_info, get_caller_address, get_contract_address,ContractAddress, account::Call, call_contract_syscall, replace_class_syscall, ClassHash, SyscallResultTrait}; + use starknet::{ + get_tx_info, get_caller_address, get_contract_address, ContractAddress, account::Call, + call_contract_syscall, replace_class_syscall, ClassHash, SyscallResultTrait + }; use ecdsa::check_ecdsa_signature; use array::{SpanTrait, ArrayTrait}; use box::BoxTrait; @@ -45,7 +63,7 @@ mod UpgradedAccount { use super::{IERC721DispatcherTrait, IERC721Dispatcher}; #[storage] - struct Storage{ + struct Storage { _public_key: felt252, _token_contract: ContractAddress, _token_id: u256, @@ -59,13 +77,18 @@ mod UpgradedAccount { #[derive(Drop, starknet::Event)] struct Upgraded { - tokenContract: ContractAddress, - tokenId: u256, + tokenContract: ContractAddress, + tokenId: u256, implementation: ClassHash } #[constructor] - fn constructor(ref self: ContractState, _public_key: felt252, token_contract: ContractAddress, token_id: u256){ + fn constructor( + ref self: ContractState, + _public_key: felt252, + token_contract: ContractAddress, + token_id: u256 + ) { self._public_key.write(_public_key); self._token_contract.write(token_contract); self._token_id.write(token_id); @@ -73,26 +96,26 @@ mod UpgradedAccount { #[external(v0)] - impl IAccountImpl of super::IUpgradedAccount{ - fn get_public_key(self: @ContractState) -> felt252{ + impl IAccountImpl of super::IUpgradedAccount { + fn get_public_key(self: @ContractState) -> felt252 { self._public_key.read() } - fn set_public_key(ref self:ContractState, new_public_key:felt252){ + fn set_public_key(ref self: ContractState, new_public_key: felt252) { self.assert_only_self(); self._public_key.write(new_public_key); } - fn isValidSignature(self: @ContractState, hash:felt252, signature: Span) -> bool { + fn isValidSignature(self: @ContractState, hash: felt252, signature: Span) -> bool { self.is_valid_signature(hash, signature) - } + } fn __validate_deploy__( self: @ContractState, - class_hash:felt252, - contract_address_salt:felt252, - public_key:felt252 - ) -> felt252{ + class_hash: felt252, + contract_address_salt: felt252, + public_key: felt252 + ) -> felt252 { self.validate_transaction() } @@ -100,11 +123,11 @@ mod UpgradedAccount { self.validate_transaction() } - fn __validate__( ref self: ContractState, mut calls:Array) -> felt252{ + fn __validate__(ref self: ContractState, mut calls: Array) -> felt252 { self.validate_transaction() } - fn __execute__(ref self: ContractState, mut calls:Array) -> Array> { + fn __execute__(ref self: ContractState, mut calls: Array) -> Array> { let caller = get_caller_address(); assert(caller.is_zero(), 'invalid caller'); @@ -114,7 +137,9 @@ mod UpgradedAccount { self._execute_calls(calls.span()) } - fn owner(ref self: ContractState, token_contract:ContractAddress, token_id:u256) -> ContractAddress { + fn owner( + ref self: ContractState, token_contract: ContractAddress, token_id: u256 + ) -> ContractAddress { IERC721Dispatcher { contract_address: token_contract }.owner_of(token_id) } @@ -122,29 +147,30 @@ mod UpgradedAccount { let contract = self._token_contract.read(); let tokenId = self._token_id.read(); return (contract, tokenId); - } + } fn upgrade(ref self: ContractState, implementation: ClassHash) { self.assert_only_self(); assert(!implementation.is_zero(), 'Invalid class hash'); replace_class_syscall(implementation).unwrap_syscall(); - self.emit( - Upgraded{ - tokenContract: self._token_contract.read(), - tokenId: self._token_id.read(), - implementation, - } - ); - } + self + .emit( + Upgraded { + tokenContract: self._token_contract.read(), + tokenId: self._token_id.read(), + implementation, + } + ); + } fn version(self: @ContractState) -> u8 { 1_u8 - } + } } #[generate_trait] - impl internalImpl of InternalTrait{ - fn assert_only_self(ref self: ContractState){ + impl internalImpl of InternalTrait { + fn assert_only_self(ref self: ContractState) { let caller = get_caller_address(); let self = get_contract_address(); assert(self == caller, 'Account: unathorized'); @@ -158,22 +184,24 @@ mod UpgradedAccount { starknet::VALIDATED } - fn is_valid_signature(self: @ContractState, hash:felt252, signature: Span) -> bool { + fn is_valid_signature( + self: @ContractState, hash: felt252, signature: Span + ) -> bool { let valid_length = signature.len() == 2_u32; let public_key = self._public_key.read(); if valid_length { check_ecdsa_signature( - message_hash: hash, - public_key: public_key, - signature_r: *signature[0_u32], + message_hash: hash, + public_key: public_key, + signature_r: *signature[0_u32], signature_s: *signature[1_u32], ) - }else{ + } else { false } } - + fn _execute_calls(ref self: ContractState, mut calls: Span) -> Array> { let mut result: Array> = ArrayTrait::new(); let mut calls = calls; @@ -181,21 +209,19 @@ mod UpgradedAccount { loop { match calls.pop_front() { Option::Some(call) => { - match call_contract_syscall(*call.to, *call.selector, call.calldata.span()) { - Result::Ok(mut retdata) => { - result.append(retdata); - }, + match call_contract_syscall( + *call.to, *call.selector, call.calldata.span() + ) { + Result::Ok(mut retdata) => { result.append(retdata); }, Result::Err(revert_reason) => { panic_with_felt252('multicall_failed'); } } }, - Option::None(_) => { - break(); - } + Option::None(_) => { break (); } }; }; result } } -} \ No newline at end of file +} diff --git a/src/test_helper/erc721_helper.cairo b/src/test_helper/erc721_helper.cairo index c1e1039..499525d 100644 --- a/src/test_helper/erc721_helper.cairo +++ b/src/test_helper/erc721_helper.cairo @@ -5,11 +5,15 @@ trait IERC721 { fn balance_of(self: @TContractState, account: ContractAddress) -> u256; fn ownerOf(self: @TContractState, token_id: u256) -> ContractAddress; fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; - fn transfer_from(ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn transfer_from( + ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ); fn approve(ref self: TContractState, to: ContractAddress, token_id: u256); fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool); fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; - fn is_approved_for_all(self: @TContractState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn is_approved_for_all( + self: @TContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool; // IERC721Metadata fn name(self: @TContractState) -> felt252; fn symbol(self: @TContractState) -> felt252; @@ -110,31 +114,40 @@ mod ERC721 { self.token_approvals.read(token_id) } - fn is_approved_for_all(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool { + fn is_approved_for_all( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { self.operator_approvals.read((owner, operator)) } fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { let owner = self.ownerOf(token_id); assert(to != owner, 'Approval to current owner'); - assert(get_caller_address() == owner || self.is_approved_for_all(owner, get_caller_address()), 'Not token owner'); - self.token_approvals.write(token_id, to); - self.emit( - Approval{ owner: self.ownerOf(token_id), to: to, token_id: token_id } + assert( + get_caller_address() == owner + || self.is_approved_for_all(owner, get_caller_address()), + 'Not token owner' ); + self.token_approvals.write(token_id, to); + self.emit(Approval { owner: self.ownerOf(token_id), to: to, token_id: token_id }); } - fn set_approval_for_all(ref self: ContractState, operator: ContractAddress, approved: bool) { + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool + ) { let owner = get_caller_address(); assert(owner != operator, 'ERC721: approve to caller'); self.operator_approvals.write((owner, operator), approved); - self.emit( - ApprovalForAll{ owner: owner, operator: operator, approved: approved } - ); + self.emit(ApprovalForAll { owner: owner, operator: operator, approved: approved }); } - fn transfer_from(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { - assert(self._is_approved_or_owner(get_caller_address(), token_id), 'neither owner nor approved'); + fn transfer_from( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + assert( + self._is_approved_or_owner(get_caller_address(), token_id), + 'neither owner nor approved' + ); self._transfer(from, to, token_id); } @@ -145,9 +158,7 @@ mod ERC721 { self.balances.write(to, receiver_balance + 1.into()); self.owners.write(token_id, to); - self.emit( - Transfer{ from: Zeroable::zero(), to: to, token_id: token_id } - ); + self.emit(Transfer { from: Zeroable::zero(), to: to, token_id: token_id }); } } @@ -157,10 +168,12 @@ mod ERC721 { self.ownerOf(token_id).is_non_zero() } - fn _is_approved_or_owner(self: @ContractState, spender: ContractAddress, token_id: u256) -> bool { + fn _is_approved_or_owner( + self: @ContractState, spender: ContractAddress, token_id: u256 + ) -> bool { let owner = self.owners.read(token_id); spender == owner - || self.is_approved_for_all(owner, spender) + || self.is_approved_for_all(owner, spender) || self.get_approved(token_id) == spender } @@ -169,7 +182,9 @@ mod ERC721 { self.token_uri.write(token_id, token_uri) } - fn _transfer(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { + fn _transfer( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { assert(from == self.ownerOf(token_id), 'ERC721: Caller is not owner'); assert(to.is_non_zero(), 'ERC721: transfer to 0 address'); @@ -178,9 +193,7 @@ mod ERC721 { self.balances.write(to, self.balances.read(to) + 1.into()); self.owners.write(token_id, to); - self.emit( - Transfer{ from: from, to: to, token_id: token_id } - ); + self.emit(Transfer { from: from, to: to, token_id: token_id }); } } -} \ No newline at end of file +} diff --git a/src/test_helper/hello_starknet.cairo b/src/test_helper/hello_starknet.cairo index 4bb53f7..da3fcd8 100644 --- a/src/test_helper/hello_starknet.cairo +++ b/src/test_helper/hello_starknet.cairo @@ -9,7 +9,7 @@ trait IHelloStarknet { mod HelloStarknet { #[storage] struct Storage { - balance: felt252, + balance: felt252, } #[external(v0)] @@ -21,11 +21,11 @@ mod HelloStarknet { fn multiply_balance(ref self: ContractState, amount: felt252) { assert(amount != 0, 'Amount cannot be 0'); - self.balance.write(self.balance.read() + amount*2); + self.balance.write(self.balance.read() + amount * 2); } fn get_balance(self: @ContractState) -> felt252 { self.balance.read() } } -} \ No newline at end of file +} diff --git a/tests/test_account.cairo b/tests/test_account.cairo index 3924f17..f51a274 100644 --- a/tests/test_account.cairo +++ b/tests/test_account.cairo @@ -4,7 +4,10 @@ use array::{ArrayTrait, SpanTrait}; use result::ResultTrait; use option::OptionTrait; use integer::u256_from_felt252; -use snforge_std::{declare, start_prank, stop_prank, start_warp, stop_warp, ContractClassTrait, ContractClass, PrintTrait, CheatTarget}; +use snforge_std::{ + declare, start_prank, stop_prank, start_warp, stop_warp, ContractClassTrait, ContractClass, + PrintTrait, CheatTarget +}; use token_bound_accounts::interfaces::IAccount::IAccountDispatcher; use token_bound_accounts::interfaces::IAccount::IAccountDispatcherTrait; @@ -60,7 +63,9 @@ fn __setup__() -> (ContractAddress, ContractAddress) { // deploy account contract let account_contract = declare('Account'); - let mut acct_constructor_calldata = array![contract_address_to_felt252(erc721_contract_address), 1, 0]; + let mut acct_constructor_calldata = array![ + contract_address_to_felt252(erc721_contract_address), 1, 0 + ]; let account_contract_address = account_contract.deploy(@acct_constructor_calldata).unwrap(); (account_contract_address, erc721_contract_address) @@ -83,7 +88,7 @@ fn test_is_valid_signature() { let data = SIGNED_TX_DATA(); let hash = data.transaction_hash; - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); start_prank(CheatTarget::One(contract_address), token_owner); @@ -111,16 +116,16 @@ fn test_execute() { // craft calldata for call array let mut calldata = array![100]; let call = Call { - to: test_address, + to: test_address, selector: 1530486729947006463063166157847785599120665941190480211966374137237989315360, calldata: calldata }; // construct call array let mut calls = array![call]; - + // get token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); // start prank @@ -128,7 +133,7 @@ fn test_execute() { // make calls dispatcher.__execute__(calls); - + // check test contract state was updated let test_dispatcher = IHelloStarknetDispatcher { contract_address: test_address }; let balance = test_dispatcher.get_balance(); @@ -147,13 +152,13 @@ fn test_execute_multicall() { // craft calldata and create call array let mut calldata = array![100]; let call1 = Call { - to: test_address, + to: test_address, selector: 1530486729947006463063166157847785599120665941190480211966374137237989315360, calldata: calldata }; let mut calldata2 = array![200]; let call2 = Call { - to: test_address, + to: test_address, selector: 1157683809588496510300162709548024577765603117833695133799390448986300456129, calldata: calldata2 }; @@ -162,7 +167,7 @@ fn test_execute_multicall() { let mut calls = array![call1, call2]; // get token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); // start prank @@ -170,7 +175,7 @@ fn test_execute_multicall() { // make calls dispatcher.__execute__(calls); - + // check test contract state was updated let test_dispatcher = IHelloStarknetDispatcher { contract_address: test_address }; let balance = test_dispatcher.get_balance(); @@ -191,7 +196,7 @@ fn test_token() { fn test_owner() { let (contract_address, erc721_contract_address) = __setup__(); let acct_dispatcher = IAccountDispatcher { contract_address: contract_address }; - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let owner = acct_dispatcher.owner(erc721_contract_address, u256_from_felt252(1)); let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); @@ -206,7 +211,7 @@ fn test_upgrade() { let new_class_hash = declare('UpgradedAccount').class_hash; // get token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); // call the upgrade function @@ -227,9 +232,9 @@ fn test_upgrade() { Result::Err(panic_data) => { stop_prank(CheatTarget::One(contract_address)); panic_data.print(); - return(); + return (); } - } + } } #[test] @@ -239,7 +244,7 @@ fn test_locking() { let lock_duration = 3000_u64; // get token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); // lock account @@ -264,7 +269,7 @@ fn test_should_not_execute_when_locked() { let lock_duration = 3000_u64; // get token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); // lock account @@ -280,7 +285,7 @@ fn test_should_not_execute_when_locked() { // confirm call to execute fails let mut calldata = array![100]; let call = Call { - to: test_address, + to: test_address, selector: 1530486729947006463063166157847785599120665941190480211966374137237989315360, calldata: calldata }; @@ -291,7 +296,7 @@ fn test_should_not_execute_when_locked() { Result::Err(panic_data) => { stop_prank(CheatTarget::One(contract_address)); panic_data.print(); - return(); + return (); } } } @@ -303,7 +308,7 @@ fn test_should_not_upgrade_when_locked() { let lock_duration = 3000_u64; // get token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); // lock account @@ -320,7 +325,7 @@ fn test_should_not_upgrade_when_locked() { Result::Err(panic_data) => { stop_prank(CheatTarget::One(contract_address)); panic_data.print(); - return(); + return (); } } } @@ -340,7 +345,7 @@ fn test_should_not_lock_if_not_owner() { stop_prank(CheatTarget::One(contract_address)); stop_warp(CheatTarget::One(contract_address)); panic_data.print(); - return(); + return (); } } } @@ -352,9 +357,9 @@ fn test_should_not_lock_if_already_locked() { let lock_duration = 3000_u64; // get token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); - + // lock account start_prank(CheatTarget::One(contract_address), token_owner); start_warp(CheatTarget::One(contract_address), 1000); @@ -369,7 +374,7 @@ fn test_should_not_lock_if_already_locked() { stop_prank(CheatTarget::One(contract_address)); stop_warp(CheatTarget::One(contract_address)); panic_data.print(); - return(); + return (); } } } @@ -381,7 +386,7 @@ fn test_should_unlock_once_duration_ends() { let lock_duration = 3000_u64; // get token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); // lock account @@ -397,4 +402,4 @@ fn test_should_unlock_once_duration_ends() { assert(status == false, 'account is meant to be unlocked'); assert(time_left == 0, 'incorrect time left'); -} \ No newline at end of file +} diff --git a/tests/test_registry.cairo b/tests/test_registry.cairo index d90525a..38ecf25 100644 --- a/tests/test_registry.cairo +++ b/tests/test_registry.cairo @@ -4,7 +4,9 @@ use array::{ArrayTrait, SpanTrait}; use result::ResultTrait; use option::OptionTrait; use integer::u256_from_felt252; -use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait, ContractClass, PrintTrait, CheatTarget}; +use snforge_std::{ + declare, start_prank, stop_prank, ContractClassTrait, ContractClass, PrintTrait, CheatTarget +}; use token_bound_accounts::interfaces::IRegistry::IRegistryDispatcherTrait; use token_bound_accounts::interfaces::IRegistry::IRegistryDispatcher; @@ -41,19 +43,26 @@ fn __setup__() -> (ContractAddress, ContractAddress) { #[test] fn test_create_account() { let (registry_contract_address, erc721_contract_address) = __setup__(); - let registry_dispatcher = IRegistryDispatcher { contract_address: registry_contract_address }; + let registry_dispatcher = IRegistryDispatcher { contract_address: registry_contract_address }; // prank contract as token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); start_prank(CheatTarget::One(registry_contract_address), token_owner); // create account let acct_class_hash = declare('Account').class_hash; - let account_address = registry_dispatcher.create_account(class_hash_to_felt252(acct_class_hash), erc721_contract_address, u256_from_felt252(1), 245828); - + let account_address = registry_dispatcher + .create_account( + class_hash_to_felt252(acct_class_hash), + erc721_contract_address, + u256_from_felt252(1), + 245828 + ); + // check total_deployed_accounts - let total_deployed_accounts = registry_dispatcher.total_deployed_accounts(erc721_contract_address, u256_from_felt252(1)); + let total_deployed_accounts = registry_dispatcher + .total_deployed_accounts(erc721_contract_address, u256_from_felt252(1)); assert(total_deployed_accounts == 1_u8, 'invalid deployed TBA count'); // confirm account deployment by checking the account owner @@ -65,41 +74,72 @@ fn test_create_account() { #[test] fn test_getting_total_deployed_accounts() { let (registry_contract_address, erc721_contract_address) = __setup__(); - let registry_dispatcher = IRegistryDispatcher { contract_address: registry_contract_address }; + let registry_dispatcher = IRegistryDispatcher { contract_address: registry_contract_address }; // prank contract as token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); start_prank(CheatTarget::One(registry_contract_address), token_owner); let acct_class_hash = declare('Account').class_hash; // create multiple accounts for same NFT - let account_address1 = registry_dispatcher.create_account(class_hash_to_felt252(acct_class_hash), erc721_contract_address, u256_from_felt252(1), 3554633); - let account_address2 = registry_dispatcher.create_account(class_hash_to_felt252(acct_class_hash), erc721_contract_address, u256_from_felt252(1), 363256); - let account_address3 = registry_dispatcher.create_account(class_hash_to_felt252(acct_class_hash), erc721_contract_address, u256_from_felt252(1), 484734); + let account_address1 = registry_dispatcher + .create_account( + class_hash_to_felt252(acct_class_hash), + erc721_contract_address, + u256_from_felt252(1), + 3554633 + ); + let account_address2 = registry_dispatcher + .create_account( + class_hash_to_felt252(acct_class_hash), + erc721_contract_address, + u256_from_felt252(1), + 363256 + ); + let account_address3 = registry_dispatcher + .create_account( + class_hash_to_felt252(acct_class_hash), + erc721_contract_address, + u256_from_felt252(1), + 484734 + ); // check total_deployed_accounts - let total_deployed_accounts = registry_dispatcher.total_deployed_accounts(erc721_contract_address, u256_from_felt252(1)); + let total_deployed_accounts = registry_dispatcher + .total_deployed_accounts(erc721_contract_address, u256_from_felt252(1)); assert(total_deployed_accounts == 3_u8, 'invalid deployed TBA count'); } #[test] fn test_get_account() { let (registry_contract_address, erc721_contract_address) = __setup__(); - let registry_dispatcher = IRegistryDispatcher { contract_address: registry_contract_address }; + let registry_dispatcher = IRegistryDispatcher { contract_address: registry_contract_address }; // prank contract as token owner - let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; + let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address }; let token_owner = token_dispatcher.ownerOf(u256_from_felt252(1)); start_prank(CheatTarget::One(registry_contract_address), token_owner); // deploy account let acct_class_hash = declare('Account').class_hash; - let account_address = registry_dispatcher.create_account(class_hash_to_felt252(acct_class_hash), erc721_contract_address, u256_from_felt252(1), 252520); + let account_address = registry_dispatcher + .create_account( + class_hash_to_felt252(acct_class_hash), + erc721_contract_address, + u256_from_felt252(1), + 252520 + ); // get account - let account = registry_dispatcher.get_account(class_hash_to_felt252(acct_class_hash), erc721_contract_address, u256_from_felt252(1), 252520); + let account = registry_dispatcher + .get_account( + class_hash_to_felt252(acct_class_hash), + erc721_contract_address, + u256_from_felt252(1), + 252520 + ); // compare both addresses assert(account == account_address, 'get_account computes wrongly'); -} \ No newline at end of file +}