Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Origin/ft signatory impl #47

Merged
merged 11 commits into from
Sep 3, 2024
1 change: 1 addition & 0 deletions src/components.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub mod lockable;
pub mod permissionable;
pub mod upgradeable;
pub mod presets;
pub mod signatory;
82 changes: 14 additions & 68 deletions src/components/account/account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ pub mod AccountComponent {
pub mod Errors {
pub const UNAUTHORIZED: felt252 = 'Account: unauthorized';
pub const INV_SIG_LEN: felt252 = 'Account: invalid sig length';
pub const INV_SIGNATURE: felt252 = 'Account: invalid signature';
pub const INV_TX_VERSION: felt252 = 'Account: invalid tx version';
}

Expand All @@ -82,27 +81,6 @@ pub mod AccountComponent {
pub impl Account<
TContractState, +HasComponent<TContractState>, +Drop<TContractState>
> of IAccount<ComponentState<TContractState>> {
/// @notice used for signature validation
/// @param hash The message hash
/// @param signature The signature to be validated
fn is_valid_signature(
self: @ComponentState<TContractState>, hash: felt252, signature: Span<felt252>
) -> felt252 {
self._is_valid_signature(hash, signature)
}

/// @notice used to validate signer
/// @param signer address to be validated
fn is_valid_signer(self: @ComponentState<TContractState>, signer: ContractAddress) -> bool {
self._is_valid_signer(signer)
}

fn __validate_declare__(
self: @ComponentState<TContractState>, class_hash: felt252
) -> felt252 {
self._validate_transaction()
}

/// @notice gets the NFT owner
/// @param token_contract the contract address of the NFT
/// @param token_id the token ID of the NFT
Expand Down Expand Up @@ -170,10 +148,6 @@ pub mod AccountComponent {
fn _execute(
ref self: ComponentState<TContractState>, mut calls: Array<Call>
) -> Array<Span<felt252>> {
// validate signer
let caller = get_caller_address();
assert(self._is_valid_signer(caller), Errors::UNAUTHORIZED);

// update state
self._update_state();

Expand Down Expand Up @@ -225,6 +199,20 @@ pub mod AccountComponent {
Serde::<ContractAddress>::deserialize(ref address).unwrap()
}

/// @notice internal function for getting the root NFT owner
/// @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_root_owner(
self: @ComponentState<TContractState>, token_contract: ContractAddress, token_id: u256
) -> ContractAddress {
// TODO: implement logic to get root owner
123.try_into().unwrap()
}

/// @notice internal transaction for returning the contract address and token ID of the NFT
fn _get_token(self: @ComponentState<TContractState>) -> (ContractAddress, u256, felt252) {
let contract = self.account_token_contract.read();
Expand All @@ -234,48 +222,6 @@ pub mod AccountComponent {
(contract, token_id, chain_id)
}

// @notice internal function for validating signer
fn _is_valid_signer(
self: @ComponentState<TContractState>, signer: ContractAddress
) -> bool {
let owner = self
._get_owner(self.account_token_contract.read(), self.account_token_id.read());
if (signer == owner) {
return true;
} else {
return false;
}
}

/// @notice internal function for signature validation
fn _is_valid_signature(
self: @ComponentState<TContractState>, hash: felt252, signature: Span<felt252>
) -> felt252 {
let signature_length = signature.len();
assert(signature_length == 2_u32, Errors::INV_SIG_LEN);

let owner = self
._get_owner(self.account_token_contract.read(), self.account_token_id.read());
let account = IAccountDispatcher { contract_address: owner };
if (account.is_valid_signature(hash, signature) == starknet::VALIDATED) {
return starknet::VALIDATED;
} else {
return 0;
}
}

/// @notice internal function for tx validation
fn _validate_transaction(self: @ComponentState<TContractState>) -> 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,
Errors::INV_SIGNATURE
);
starknet::VALIDATED
}

/// @notice internal function for executing transactions
/// @param calls An array of transactions to be executed
fn _execute_calls(
Expand Down
9 changes: 3 additions & 6 deletions src/components/lockable/lockable.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// lockable component
// *************************************************************************
// LOCKABLE COMPONENT
// *************************************************************************
Expand Down Expand Up @@ -72,13 +71,10 @@ pub mod LockableComponent {
+Drop<TContractState>,
impl Account: AccountComponent::HasComponent<TContractState>
> of ILockable<ComponentState<TContractState>> {
// @notice locks an account
// @param lock_until duration for which account should be locked
fn lock(ref self: ComponentState<TContractState>, lock_until: u64) {
let current_timestamp = get_block_timestamp();
let account_comp = get_dep_component!(@self, Account);

let is_valid = account_comp._is_valid_signer(get_caller_address());
assert(is_valid, Errors::UNAUTHORIZED);

assert(
lock_until <= current_timestamp + YEAR_DAYS_SECONDS, Errors::EXCEEDS_MAX_LOCK_TIME
);
Expand All @@ -103,6 +99,7 @@ pub mod LockableComponent {
);
}

// @notice returns the lock status of an account
fn is_locked(self: @ComponentState<TContractState>) -> (bool, u64) {
let unlock_timestamp = self.lock_until.read();
let current_time = get_block_timestamp();
Expand Down
111 changes: 110 additions & 1 deletion src/components/permissionable/permissionable.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,112 @@
// permissionable component
// *************************************************************************
// PERMISSIONABLE COMPONENT
// *************************************************************************
#[starknet::component]
pub mod PermissionableComponent {
// *************************************************************************
// IMPORTS
// *************************************************************************
use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess};
use starknet::{ContractAddress, get_caller_address, get_block_timestamp};
use token_bound_accounts::components::account::account::AccountComponent;
use token_bound_accounts::interfaces::IAccount::{IAccount, IAccountDispatcherTrait};
use token_bound_accounts::components::account::account::AccountComponent::InternalImpl;
use token_bound_accounts::interfaces::IPermissionable::{
IPermissionable, IPermissionableDispatcher, IPermissionableDispatcherTrait
};

// *************************************************************************
// STORAGE
// *************************************************************************
#[storage]
pub struct Storage {
permissions: Map<
(ContractAddress, ContractAddress), bool
> // <<owner, permissioned_address>, bool>
}

// *************************************************************************
// EVENTS
// *************************************************************************
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
PermissionUpdated: PermissionUpdated
}

// @notice emitted when permissions are updated for an account
// @param owner tokenbound account owner
// @param permissioned_address address to be given/revoked permission
// @param has_permission returns true if user has permission else false
#[derive(Drop, starknet::Event)]
pub struct PermissionUpdated {
#[key]
pub owner: ContractAddress,
pub permissioned_address: ContractAddress,
pub has_permission: bool,
}

// *************************************************************************
// ERRORS
// *************************************************************************
pub mod Errors {
pub const INVALID_LENGTH: felt252 = 'Account: invalid length';
pub const UNAUTHORIZED: felt252 = 'Account: unauthorized';
}


// *************************************************************************
// EXTERNAL FUNCTIONS
// *************************************************************************
#[embeddable_as(PermissionableImpl)]
pub impl Permissionable<
TContractState,
+HasComponent<TContractState>,
+Drop<TContractState>,
impl Account: AccountComponent::HasComponent<TContractState>
> of IPermissionable<ComponentState<TContractState>> {
// @notice sets permission for an account
// @permissioned_addresses array of addresses who's permission is to be updated
// @param permssions permission value <true, false>
fn set_permission(
ref self: ComponentState<TContractState>,
permissioned_addresses: Array<ContractAddress>,
permissions: Array<bool>
) {
assert(permissioned_addresses.len() == permissions.len(), Errors::INVALID_LENGTH);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was going over our implementation again and it just came to mind that we should enforce only the contract owner calling this function. else permissioned addresses might give permissions to random addresses on the owner's account.

So let's enforce an assert that checks that the caller is the account owner.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you marked this as resolved, but it's still yet to be resolved. assert that only owner can call this function


let account_comp = get_dep_component!(@self, Account);
let owner = account_comp.owner();
assert(owner == get_caller_address(), Errors::UNAUTHORIZED);

let length = permissioned_addresses.len();
let mut index: u32 = 0;
while index < length {
self
.permissions
.write((owner, *permissioned_addresses[index]), *permissions[index]);
self
.emit(
PermissionUpdated {
owner: owner,
permissioned_address: *permissioned_addresses[index],
has_permission: *permissions[index]
}
);
index += 1
}
}

// @notice returns if a user has permission or not
// @param owner tokenbound account owner
// @param permissioned_address address to check permission for
fn has_permission(
self: @ComponentState<TContractState>,
owner: ContractAddress,
permissioned_address: ContractAddress
) -> bool {
let permission = self.permissions.read((owner, permissioned_address));
permission
}
}
}
Loading
Loading