Skip to content

Commit

Permalink
chore: add signatory component
Browse files Browse the repository at this point in the history
  • Loading branch information
Darlington02 committed Aug 28, 2024
1 parent e16439b commit 89918ff
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 44 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;
43 changes: 14 additions & 29 deletions src/components/account/account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,6 @@ pub mod AccountComponent {
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 +158,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 +209,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,19 +232,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>
Expand Down
5 changes: 0 additions & 5 deletions src/components/lockable/lockable.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,6 @@ pub mod LockableComponent {
> of ILockable<ComponentState<TContractState>> {
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 Down
38 changes: 36 additions & 2 deletions src/components/presets/account_preset.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ pub mod AccountPreset {
use token_bound_accounts::components::account::account::AccountComponent;
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::interfaces::{
IUpgradeable::IUpgradeable, IExecutable::IExecutable, ILockable::ILockable
IUpgradeable::IUpgradeable, IExecutable::IExecutable, ILockable::ILockable, ISignatory::ISignatory
};

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);

// Account
#[abi(embed_v0)]
Expand All @@ -22,6 +24,7 @@ pub mod AccountPreset {
impl AccountInternalImpl = AccountComponent::InternalImpl<ContractState>;
impl UpgradeableInternalImpl = UpgradeableComponent::Private<ContractState>;
impl LockableImpl = LockableComponent::LockableImpl<ContractState>;
impl SignerImpl = SignatoryComponent::Private<ContractState>;

// *************************************************************************
// STORAGE
Expand All @@ -34,6 +37,8 @@ pub mod AccountPreset {
upgradeable: UpgradeableComponent::Storage,
#[substorage(v0)]
lockable: LockableComponent::Storage,
#[substorage(v0)]
signatory: SignatoryComponent::Storage
}

// *************************************************************************
Expand All @@ -47,7 +52,9 @@ pub mod AccountPreset {
#[flat]
UpgradeableEvent: UpgradeableComponent::Event,
#[flat]
LockableEvent: LockableComponent::Event
LockableEvent: LockableComponent::Event,
#[flat]
SignatoryEvent: SignatoryComponent::Event
}

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

// *************************************************************************
// SIGNATORY IMPL
// *************************************************************************
#[abi(embed_v0)]
impl Signatory of ISignatory<ContractState> {
fn is_valid_signer(self: @ContractState, signer: ContractAddress) -> bool {
self.signatory._permissioned_signer_validation(signer)
}
}

// *************************************************************************
// EXECUTABLE IMPL
// *************************************************************************
#[abi(embed_v0)]
impl Executable of IExecutable<ContractState> {
fn execute(ref self: ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
// validate signer
let caller = get_caller_address();
assert(self.is_valid_signer(caller), 'Account: unauthorized');

// cannot make this call when the account is lock
let (is_locked, _) = self.lockable.is_locked();
assert(is_locked != true, 'Account: locked');

// execute calls
self.account._execute(calls)
}
}
Expand All @@ -77,9 +100,15 @@ pub mod AccountPreset {
#[abi(embed_v0)]
impl Upgradeable of IUpgradeable<ContractState> {
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
// validate signer
let caller = get_caller_address();
assert(self.is_valid_signer(caller), 'Account: unauthorized');

// cannot make this call when the account is lock
let (is_locked, _) = self.lockable.is_locked();
assert(is_locked != true, 'Account: locked');

// upgrade account
self.upgradeable._upgrade(new_class_hash);
}
}
Expand All @@ -90,6 +119,11 @@ pub mod AccountPreset {
#[abi(embed_v0)]
impl Lockable of ILockable<ContractState> {
fn lock(ref self: ContractState, lock_until: u64) {
// validate signer
let caller = get_caller_address();
assert(self.is_valid_signer(caller), 'Account: unauthorized');

// lock account
self.lockable.lock(lock_until);
}
fn is_locked(self: @ContractState) -> (bool, u64) {
Expand Down
1 change: 1 addition & 0 deletions src/components/signatory.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod signatory;
80 changes: 80 additions & 0 deletions src/components/signatory/signatory.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// *************************************************************************
// SIGNATORY COMPONENT
// *************************************************************************
#[starknet::component]
pub mod SignatoryComponent {
// *************************************************************************
// IMPORTS
// *************************************************************************
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;

// *************************************************************************
// STORAGE
// *************************************************************************
#[storage]
pub struct Storage {}

// *************************************************************************
// PRIVATE FUNCTIONS
// *************************************************************************
#[generate_trait]
pub impl Private<
TContractState,
+HasComponent<TContractState>,
+Drop<TContractState>,
impl Account: AccountComponent::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 {
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);

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

/// @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 {
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);

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

/// @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
}
}
}
6 changes: 0 additions & 6 deletions src/components/upgradeable/upgradeable.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ pub mod UpgradeableComponent {
// *************************************************************************
pub mod Errors {
pub const INVALID_CLASS: felt252 = 'Class hash cannot be zero';
pub const UNAUTHORIZED: felt252 = 'Account: unauthorized';
}

// *************************************************************************
Expand All @@ -57,11 +56,6 @@ pub mod UpgradeableComponent {
/// @notice replaces the contract's class hash with `new_class_hash`.
/// Emits an `Upgraded` event.
fn _upgrade(ref self: ComponentState<TContractState>, new_class_hash: ClassHash) {
// validate new signer
let account_comp = get_dep_component!(@self, Account);
let is_valid = account_comp._is_valid_signer(get_caller_address());
assert(is_valid, Errors::UNAUTHORIZED);

// update state
let mut account_comp_mut = get_dep_component_mut!(ref self, Account);
account_comp_mut._update_state();
Expand Down
1 change: 1 addition & 0 deletions src/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ pub mod IRegistry;
pub mod IUpgradeable;
pub mod IExecutable;
pub mod ILockable;
pub mod ISignatory;
2 changes: 0 additions & 2 deletions src/interfaces/IAccount.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ pub trait IAccount<TContractState> {
fn is_valid_signature(
self: @TContractState, hash: felt252, signature: Span<felt252>
) -> felt252;
fn is_valid_signer(self: @TContractState, signer: ContractAddress) -> bool;
fn __validate_declare__(self: @TContractState, class_hash: felt252) -> felt252;
fn token(self: @TContractState) -> (ContractAddress, u256, felt252);
fn owner(self: @TContractState) -> ContractAddress;
fn state(self: @TContractState) -> u256;
Expand Down
9 changes: 9 additions & 0 deletions src/interfaces/ISignatory.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// *************************************************************************
// SIGNER VALIDATION INTERFACE
// *************************************************************************
use starknet::ContractAddress;

#[starknet::interface]
pub trait ISignatory<TContractState> {
fn is_valid_signer(self: @TContractState, signer: ContractAddress) -> bool;
}

0 comments on commit 89918ff

Please sign in to comment.