Skip to content

Commit

Permalink
Merge pull request #47 from horuslabsio/origin/ft_signatory_impl
Browse files Browse the repository at this point in the history
Origin/ft signatory impl
  • Loading branch information
Darlington02 authored Sep 3, 2024
2 parents e16439b + a7f2d89 commit dea487f
Show file tree
Hide file tree
Showing 17 changed files with 836 additions and 124 deletions.
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;
90 changes: 22 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 @@ -133,6 +111,12 @@ pub mod AccountComponent {
return false;
}
}

fn get_root_owner(
self: @ComponentState<TContractState>, token_contract: ContractAddress, token_id: u256
) -> ContractAddress {
self._get_root_owner(token_contract, token_id)
}
}

// *************************************************************************
Expand Down Expand Up @@ -170,10 +154,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 +205,22 @@ 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

1.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 +230,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);

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

0 comments on commit dea487f

Please sign in to comment.