Skip to content

Commit

Permalink
chore: update account contract events, run scarb fmt
Browse files Browse the repository at this point in the history
  • Loading branch information
Darlington02 committed Jan 16, 2024
1 parent 31be1a2 commit ef1ed67
Show file tree
Hide file tree
Showing 15 changed files with 369 additions and 215 deletions.
2 changes: 1 addition & 1 deletion src/account.cairo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
mod account;
mod account;
126 changes: 76 additions & 50 deletions src/account/account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -69,19 +84,19 @@ mod Account {
}

#[external(v0)]
impl IAccountImpl of IAccount<ContractState>{
impl IAccountImpl of IAccount<ContractState> {
/// @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>) -> felt252 {
fn is_valid_signature(
self: @ContractState, hash: felt252, signature: Span<felt252>
) -> 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()
}

Expand All @@ -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<Call>) -> felt252{
fn __validate__(ref self: ContractState, mut calls: Array<Call>) -> 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<Call>) -> Array<Span<felt252>> {
fn __execute__(ref self: ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
self._assert_only_owner();
let (lock_status, _) = self._is_locked();
assert(!lock_status, 'Account: account is locked!');
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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');
Expand All @@ -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<felt252> = 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();
Expand All @@ -201,37 +224,42 @@ 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
fn _validate_transaction(self: @ContractState) -> felt252 {
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>) -> felt252 {
fn _is_valid_signature(
self: @ContractState, hash: felt252, signature: Span<felt252>
) -> 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<Call>) -> Array<Span<felt252>> {
Expand All @@ -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
}
}
}
}
2 changes: 1 addition & 1 deletion src/interfaces.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
mod IAccount;
mod IERC721;
mod IRegistry;
mod IRegistry;
22 changes: 14 additions & 8 deletions src/interfaces/IAccount.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ use starknet::ClassHash;
use starknet::account::Call;

#[starknet::interface]
trait IAccount<TContractState>{
fn is_valid_signature(self: @TContractState, hash:felt252, signature: Span<felt252>) -> felt252;
fn __validate__(ref self: TContractState, calls:Array<Call>) -> 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<Call>) -> Array<Span<felt252>>;
fn token(self:@TContractState) -> (ContractAddress, u256);
fn owner(self: @TContractState, token_contract:ContractAddress, token_id:u256) -> ContractAddress;
trait IAccount<TContractState> {
fn is_valid_signature(
self: @TContractState, hash: felt252, signature: Span<felt252>
) -> felt252;
fn __validate__(ref self: TContractState, calls: Array<Call>) -> 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<Call>) -> Array<Span<felt252>>;
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);
Expand Down
16 changes: 12 additions & 4 deletions src/interfaces/IERC721.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@ trait IERC721<TContractState> {
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<felt252>
ref self: TContractState,
from: ContractAddress,
to: ContractAddress,
token_id: u256,
data: Span<felt252>
);
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;
}
}
22 changes: 18 additions & 4 deletions src/interfaces/IRegistry.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,21 @@ use starknet::ContractAddress;

#[starknet::interface]
trait IRegistry<TContractState> {
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;
}
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;
}
2 changes: 1 addition & 1 deletion src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod registry;
mod account;
mod interfaces;
mod test_helper;
mod test_helper;
2 changes: 1 addition & 1 deletion src/registry.cairo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
mod registry;
mod registry;
Loading

0 comments on commit ef1ed67

Please sign in to comment.