Skip to content

Commit

Permalink
chore: add permissionable component, update signatory comp.
Browse files Browse the repository at this point in the history
  • Loading branch information
Darlington02 committed Aug 28, 2024
1 parent 89918ff commit 981262f
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/components.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ pub mod lockable;
pub mod permissionable;
pub mod upgradeable;
pub mod presets;
pub mod signatory;
pub mod signatory;
4 changes: 3 additions & 1 deletion 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,6 +71,8 @@ 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();
assert(
Expand All @@ -98,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 UNAUTHORIZED: felt252 = 'Permission: unauthorized';
pub const NOT_OWNER: felt252 = 'Permission: not account owner';
pub const INVALID_LENGTH: felt252 = 'Permission: invalid length';
pub const NOT_PERMITTED: felt252 = 'Permisson: not permitted';
}


// *************************************************************************
// 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();
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
}
}
}
41 changes: 37 additions & 4 deletions src/components/presets/account_preset.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ pub mod AccountPreset {
use token_bound_accounts::components::upgradeable::upgradeable::UpgradeableComponent;
use token_bound_accounts::components::lockable::lockable::LockableComponent;
use token_bound_accounts::components::signatory::signatory::SignatoryComponent;
use token_bound_accounts::components::permissionable::permissionable::PermissionableComponent;
use token_bound_accounts::interfaces::{
IUpgradeable::IUpgradeable, IExecutable::IExecutable, ILockable::ILockable, ISignatory::ISignatory
IUpgradeable::IUpgradeable, IExecutable::IExecutable, ILockable::ILockable,
ISignatory::ISignatory, IPermissionable::IPermissionable
};

component!(path: AccountComponent, storage: account, event: AccountEvent);
component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);
component!(path: LockableComponent, storage: lockable, event: LockableEvent);
component!(path: SignatoryComponent, storage: signatory, event: SignatoryEvent);
component!(path: PermissionableComponent, storage: permissionable, event: PermissionableEvent);

// Account
#[abi(embed_v0)]
Expand All @@ -38,7 +41,9 @@ pub mod AccountPreset {
#[substorage(v0)]
lockable: LockableComponent::Storage,
#[substorage(v0)]
signatory: SignatoryComponent::Storage
signatory: SignatoryComponent::Storage,
#[substorage(v0)]
permissionable: PermissionableComponent::Storage,
}

// *************************************************************************
Expand All @@ -54,7 +59,9 @@ pub mod AccountPreset {
#[flat]
LockableEvent: LockableComponent::Event,
#[flat]
SignatoryEvent: SignatoryComponent::Event
SignatoryEvent: SignatoryComponent::Event,
#[flat]
PermissionableEvent: PermissionableComponent::Event
}

// *************************************************************************
Expand All @@ -65,7 +72,7 @@ pub mod AccountPreset {
self.account.initializer(token_contract, token_id);
}

// *************************************************************************
// *************************************************************************
// SIGNATORY IMPL
// *************************************************************************
#[abi(embed_v0)]
Expand Down Expand Up @@ -126,8 +133,34 @@ pub mod AccountPreset {
// lock account
self.lockable.lock(lock_until);
}

fn is_locked(self: @ContractState) -> (bool, u64) {
self.lockable.is_locked()
}
}

// *************************************************************************
// PERMISSIONABLE IMPL
// *************************************************************************
#[abi(embed_v0)]
impl Permissionable of IPermissionable<ContractState> {
fn set_permission(
ref self: ContractState,
permissioned_addresses: Array<ContractAddress>,
permissions: Array<bool>
) {
// validate signer
let caller = get_caller_address();
assert(self.is_valid_signer(caller), 'Account: unauthorized');

// set permissions
self.permissionable.set_permission(permissioned_addresses, permissions)
}

fn has_permission(
self: @ContractState, owner: ContractAddress, permissioned_address: ContractAddress
) -> bool {
self.permissionable.has_permission(owner, permissioned_address)
}
}
}
2 changes: 1 addition & 1 deletion src/components/signatory.cairo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pub mod signatory;
pub mod signatory;
65 changes: 45 additions & 20 deletions src/components/signatory/signatory.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ pub mod SignatoryComponent {
// *************************************************************************
// IMPORTS
// *************************************************************************
use starknet::{
get_caller_address, get_contract_address, ContractAddress
};
use starknet::{get_caller_address, get_contract_address, ContractAddress};
use token_bound_accounts::components::account::account::AccountComponent;
use token_bound_accounts::components::account::account::AccountComponent::InternalImpl;
use token_bound_accounts::components::permissionable::permissionable::PermissionableComponent;
use token_bound_accounts::components::permissionable::permissionable::PermissionableComponent::PermissionableImpl;

// *************************************************************************
// STORAGE
Expand All @@ -26,17 +26,19 @@ pub mod SignatoryComponent {
TContractState,
+HasComponent<TContractState>,
+Drop<TContractState>,
impl Account: AccountComponent::HasComponent<TContractState>
impl Account: AccountComponent::HasComponent<TContractState>,
impl Permissionable: PermissionableComponent::HasComponent<TContractState>
> of PrivateTrait<TContractState> {
/// @notice implements a simple signer validation where only NFT owner is a valid signer.
/// @param signer the address to be validated
fn _base_signer_validation(self: @ComponentState<TContractState>, signer: ContractAddress) -> bool {
fn _base_signer_validation(
self: @ComponentState<TContractState>, signer: ContractAddress
) -> bool {
let account = get_dep_component!(self, Account);
let (contract_address, token_id, _) = account._get_token();

// get owner
let owner = account
._get_owner(contract_address, token_id);
let owner = account._get_owner(contract_address, token_id);

// validate
if (signer == owner) {
Expand All @@ -46,35 +48,58 @@ pub mod SignatoryComponent {
}
}

/// @notice implements a signer validation where both NFT owner and the root owner (for nested accounts) are valid signers.
/// @notice implements a signer validation where both NFT owner and the root owner (for
/// nested accounts) are valid signers.
/// @param signer the address to be validated
fn _base_and_root_signer_validation(self: @ComponentState<TContractState>, signer: ContractAddress) -> bool {
fn _base_and_root_signer_validation(
self: @ComponentState<TContractState>, signer: ContractAddress
) -> bool {
let account = get_dep_component!(self, Account);
let (contract_address, token_id, _) = account._get_token();

// get owner
let owner = account
._get_owner(contract_address, token_id);
let owner = account._get_owner(contract_address, token_id);
// get root owner
let root_owner = account
._get_root_owner(contract_address, token_id);
let root_owner = account._get_root_owner(contract_address, token_id);

// validate
if (signer == owner) {
return true;
}
else if(signer == root_owner) {
} else if (signer == root_owner) {
return true;
}
else {
} else {
return false;
}
}

/// @notice implements a more complex signer validation where NFT owner, root owner, and permissioned addresses are valid signers.
/// @notice implements a more complex signer validation where NFT owner, root owner, and
/// permissioned addresses are valid signers.
/// @param signer the address to be validated
fn _permissioned_signer_validation(self: @ComponentState<TContractState>, signer: ContractAddress) -> bool {
true
fn _permissioned_signer_validation(
self: @ComponentState<TContractState>, signer: ContractAddress
) -> bool {
let account = get_dep_component!(self, Account);
let (contract_address, token_id, _) = account._get_token();

// get owner
let owner = account._get_owner(contract_address, token_id);
// get root owner
let root_owner = account._get_root_owner(contract_address, token_id);

// check if signer has permissions
let permission = get_dep_component!(self, Permissionable);
let is_permissioned = permission.has_permission(owner, signer);

// validate
if (signer == owner) {
return true;
} else if (signer == root_owner) {
return true;
} else if (is_permissioned) {
return true;
} else {
return false;
}
}
}
}
3 changes: 2 additions & 1 deletion src/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ pub mod IRegistry;
pub mod IUpgradeable;
pub mod IExecutable;
pub mod ILockable;
pub mod ISignatory;
pub mod ISignatory;
pub mod IPermissionable;
3 changes: 3 additions & 0 deletions src/interfaces/ILockable.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// *************************************************************************
// LOCKABLE INTERFACE
// *************************************************************************
use starknet::ContractAddress;

#[starknet::interface]
Expand Down
16 changes: 16 additions & 0 deletions src/interfaces/IPermissionable.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// *************************************************************************
// PERMISSIONABLE INTERFACE
// *************************************************************************
use starknet::ContractAddress;

#[starknet::interface]
pub trait IPermissionable<TContractState> {
fn set_permission(
ref self: TContractState,
permissioned_addresses: Array<ContractAddress>,
permissions: Array<bool>
);
fn has_permission(
self: @TContractState, owner: ContractAddress, permissioned_address: ContractAddress
) -> bool;
}
14 changes: 0 additions & 14 deletions tests/test_account_component.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -159,20 +159,6 @@ fn test_is_valid_signature() {
stop_cheat_caller_address(contract_address);
}

#[test]
fn test_is_valid_signer() {
let (contract_address, erc721_contract_address) = __setup__();
let dispatcher = IAccountDispatcher { contract_address };
let token_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address };
let token_owner = token_dispatcher.ownerOf(1.try_into().unwrap());

// check for valid signer
let valid_signer = dispatcher.is_valid_signer(token_owner);
let invalid_signer = dispatcher.is_valid_signer(ACCOUNT.try_into().unwrap());
assert(valid_signer == true, 'signer is meant to be valid!');
assert(invalid_signer == false, 'signer is meant to be invalid!');
}

#[test]
fn test_execute() {
let (contract_address, erc721_contract_address) = __setup__();
Expand Down

0 comments on commit 981262f

Please sign in to comment.