Skip to content

Commit

Permalink
ModularCompliance
Browse files Browse the repository at this point in the history
  • Loading branch information
EgeCaner committed Dec 6, 2024
1 parent 68e6a22 commit 6b5875e
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 72 deletions.
2 changes: 2 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ version = "0.1.0"
dependencies = [
"openzeppelin_access",
"openzeppelin_upgrades",
"registry",
"roles",
"storage",
"token",
]

Expand Down
2 changes: 2 additions & 0 deletions crates/compliance/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ edition = "2024_07"
starknet.workspace = true
token = { path = "../token"}
roles = { path = "../roles"}
registry = { path = "../registry"}
storage = { path = "../storage"}
openzeppelin_access.workspace = true
openzeppelin_upgrades.workspace = true

Expand Down
50 changes: 8 additions & 42 deletions crates/compliance/src/imodular_compliance.cairo
Original file line number Diff line number Diff line change
@@ -1,44 +1,5 @@
use starknet::ContractAddress;

#[event]
#[derive(Drop, starknet::Event)]
pub enum ModularComplianceEvent {
ModuleInteraction: ModuleInteraction,
TokenBound: TokenBound,
TokenUnbound: TokenUnbound,
ModuleAdded: ModuleAdded,
ModuleRemoved: ModuleRemoved,
}

#[derive(Drop, starknet::Event)]
pub struct ModuleInteraction {
#[key]
target: ContractAddress,
selector: u32
}

#[derive(Drop, starknet::Event)]
pub struct TokenBound {
token: ContractAddress,
}

#[derive(Drop, starknet::Event)]
pub struct TokenUnbound {
token: ContractAddress,
}

#[derive(Drop, starknet::Event)]
pub struct ModuleAdded {
#[key]
module: ContractAddress,
}

#[derive(Drop, starknet::Event)]
pub struct ModuleRemoved {
#[key]
module: ContractAddress,
}

#[starknet::interface]
pub trait IModularCompliance<TContractState> {
/// @dev binds a token to the compliance contract
Expand Down Expand Up @@ -73,7 +34,12 @@ pub trait IModularCompliance<TContractState> {
/// @param _module The address of the module
/// This function can be called only by the modular compliance owner
/// emits a `ModuleInteraction` event
fn call_module_function(ref self: TContractState, calldata: ByteArray, module: ContractAddress);
fn call_module_function(
ref self: TContractState,
selector: felt252,
calldata: Span<felt252>,
module: ContractAddress,
);

/// @dev function called whenever tokens are transferred
/// from one wallet to another
Expand All @@ -87,7 +53,7 @@ pub trait IModularCompliance<TContractState> {
/// @param _amount The amount of tokens involved in the transfer
/// This function calls moduleTransferAction() on each module bound to the compliance contract
fn transferred(
ref self: TContractState, from: ContractAddress, to: ContractAddress, amount: u256
ref self: TContractState, from: ContractAddress, to: ContractAddress, amount: u256,
);

/// @dev function called whenever tokens are created on a wallet
Expand Down Expand Up @@ -123,7 +89,7 @@ pub trait IModularCompliance<TContractState> {
/// If each of the module checks return TRUE, this function will return TRUE as well
/// returns FALSE otherwise
fn can_transfer(
self: @TContractState, from: ContractAddress, to: ContractAddress, amount: u256
self: @TContractState, from: ContractAddress, to: ContractAddress, amount: u256,
) -> bool;

/// @dev getter for the modules bound to the compliance contract
Expand Down
1 change: 0 additions & 1 deletion crates/compliance/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
pub mod imodular_compliance;
pub mod modular_compliance;
//pub mod removable_vec;
pub mod modules {
pub mod abstract_module;
pub mod conditional_transfer_module;
Expand Down
244 changes: 215 additions & 29 deletions crates/compliance/src/modular_compliance.cairo
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
#[starknet::contract]
mod ModularCompliance {
//use compliance::modular::imodular_compliance::IModularCompliance;
use starknet::ContractAddress;
use starknet::storage::{
Vec, //VecTrait, MutableVecTrait,
Map, //StoragePathEntry, StorageMapReadAccess,
//StorageMapWriteAccess
use core::num::traits::Zero;
use crate::{
imodular_compliance::IModularCompliance,
modules::imodule::{IModuleDispatcher, IModuleDispatcherTrait},
};
use openzeppelin_access::ownable::OwnableComponent;
use openzeppelin_upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent};
use starknet::{
ClassHash, ContractAddress,
storage::{Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess},
};
use storage::storage_array::{
ContractAddressVecToContractAddressArray, MutableStorageArrayTrait,
StorageArrayContractAddress, StorageArrayTrait,
};

component!(path: UpgradeableComponent, storage: upgrades, event: UpgradeableEvent);

impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl<ContractState>;

component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);

#[abi(embed_v0)]
impl OwnableImpl = OwnableComponent::OwnableImpl<ContractState>;
impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;

#[storage]
struct Storage {
/// token linked to the compliance contract
token_bound: ContractAddress,
/// Array of modules bound to the compliance
modules: Vec<ContractAddress>,
modules: StorageArrayContractAddress,
/// Mapping of module binding status
module_bound: Map<ContractAddress, bool>,
#[substorage(v0)]
upgrades: UpgradeableComponent::Storage,
#[substorage(v0)]
ownable: OwnableComponent::Storage,
}

#[event]
Expand All @@ -25,54 +47,218 @@ mod ModularCompliance {
TokenBound: TokenBound,
TokenUnbound: TokenUnbound,
ModuleAdded: ModuleAdded,
ModuleRemoved: ModuleRemoved
ModuleRemoved: ModuleRemoved,
#[flat]
UpgradeableEvent: UpgradeableComponent::Event,
#[flat]
OwnableEvent: OwnableComponent::Event,
}
/// @dev Event emitted for each executed interaction with a module contract.
/// For gas efficiency, only the interaction calldata selector (first 4
/// bytes) is included in the event. For interactions without calldata or
/// whose calldata is shorter than 4 bytes, the selector will be `0`.

#[derive(Drop, starknet::Event)]
struct ModuleInteraction {
#[key]
target: ContractAddress,
selector: i32
selector: felt252,
}

/// this event is emitted when a token has been bound to the compliance contract
/// the event is emitted by the bind_token function
/// `token` is the address of the token to bind
#[derive(Drop, starknet::Event)]
struct TokenBound {
token: ContractAddress,
}

/// this event is emitted when a token has been unbound from the compliance contract
/// the event is emitted by the unbind_token function
/// `token` is the address of the token to unbind
#[derive(Drop, starknet::Event)]
struct TokenUnbound {
token: ContractAddress,
}

/// this event is emitted when a module has been added to the list of modules bound to the
/// compliance contract the event is emitted by the add_module function
/// `module` is the address of the compliance module
#[derive(Drop, starknet::Event)]
struct ModuleAdded {
#[key]
module: ContractAddress,
}

/// this event is emitted when a module has been removed from the list of modules bound to the
/// compliance contract the event is emitted by the remove_module function
/// `module` is the address of the compliance module
#[derive(Drop, starknet::Event)]
struct ModuleRemoved {
#[key]
module: ContractAddress,
}
//#[abi(embed_v0)]
//impl ModularComplianceImpl of IModularCompliance<ContractState>{
//
//}

#[abi(embed_v0)]
impl UpgradeableImpl of IUpgradeable<ContractState> {
/// Upgrades the implementation used by this contract.
///
/// # Arguments
///
/// - `new_class_hash` A `ClassHash` representing the implementation to update to.
///
/// # Requirements
///
/// - This function can only be called by the xerc20 owner.
/// - The `ClassHash` should already have been declared.
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
self.ownable.assert_only_owner();
self.upgrades.upgrade(new_class_hash);
}
}

#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
self.ownable.initializer(owner);
}

#[abi(embed_v0)]
impl ModularComplianceImpl of IModularCompliance<ContractState> {
fn bind_token(ref self: ContractState, token: ContractAddress) {
assert(token.is_non_zero(), 'Token zero address');
let caller = starknet::get_caller_address();
assert(
self.ownable.owner() == caller
|| (self.token_bound.read().is_zero() && caller == token),
'Only owner or token can call',
);
self.token_bound.write(token);
self.emit(TokenBound { token });
}

fn unbind_token(ref self: ContractState, token: ContractAddress) {
assert(token.is_non_zero(), 'Token zero address');
let caller = starknet::get_caller_address();
assert(
self.ownable.owner() == caller
|| (self.token_bound.read().is_zero() && caller == token),
'Only owner or token can call',
);
assert(self.token_bound.read() == token, 'This token is not bound');
self.token_bound.write(Zero::zero());
self.emit(TokenUnbound { token });
}

fn add_module(ref self: ContractState, module: ContractAddress) {
self.ownable.assert_only_owner();
assert(module.is_non_zero(), 'Module address zero');
assert(!self.module_bound.entry(module).read(), 'module already bound');
let modules_storage_path = self.modules.deref();
assert(modules_storage_path.len() < 25, 'Cannot add more than 25 modules');
let module_dispatcher = IModuleDispatcher { contract_address: module };
if !module_dispatcher.is_plug_and_play() {
assert!(
module_dispatcher.can_compliance_bind(starknet::get_contract_address()),
"Compliance is not suitable for binding to the module",
);
}

module_dispatcher.bind_compliance(starknet::get_contract_address());
modules_storage_path.append().write(module);
self.module_bound.entry(module).write(true);
self.emit(ModuleAdded { module });
}

fn remove_module(ref self: ContractState, module: ContractAddress) {
self.ownable.assert_only_owner();
assert(module.is_non_zero(), 'Module address zero');
assert(self.module_bound.entry(module).read(), 'module not bound');
self.module_bound.entry(module).write(false);
IModuleDispatcher { contract_address: module }
.unbind_compliance(starknet::get_contract_address());

let modules_storage_path = self.modules.deref();
for i in 0..modules_storage_path.len() {
if modules_storage_path.at(i).read() == module {
modules_storage_path.delete(i);
self.emit(ModuleRemoved { module });
break;
}
};
}

fn call_module_function(
ref self: ContractState,
selector: felt252,
calldata: Span<felt252>,
module: ContractAddress,
) {
self.ownable.assert_only_owner();
assert(self.module_bound.entry(module).read(), 'Can only call bound module');
starknet::syscalls::call_contract_syscall(module, selector, calldata).unwrap();
self.emit(ModuleInteraction { target: module, selector });
}

fn transferred(
ref self: ContractState, from: ContractAddress, to: ContractAddress, amount: u256,
) {
self.assert_only_token();
assert(from.is_non_zero() && to.is_non_zero(), 'Zero address');
assert(amount.is_non_zero(), 'No value transfer');

let modules_storage_path = self.modules.deref();
for i in 0..modules_storage_path.len() {
IModuleDispatcher { contract_address: modules_storage_path.at(i).read() }
.module_transfer_action(from, to, amount);
};
}

fn created(ref self: ContractState, to: ContractAddress, amount: u256) {
self.assert_only_token();
assert(to.is_non_zero(), 'Zero address');
assert(amount.is_non_zero(), 'No value transfer');

let modules_storage_path = self.modules.deref();
for i in 0..modules_storage_path.len() {
IModuleDispatcher { contract_address: modules_storage_path.at(i).read() }
.module_mint_action(to, amount);
};
}

fn destroyed(ref self: ContractState, from: ContractAddress, amount: u256) {
self.assert_only_token();
assert(from.is_non_zero(), 'Zero address');
assert(amount.is_non_zero(), 'No value transfer');

let modules_storage_path = self.modules.deref();
for i in 0..modules_storage_path.len() {
IModuleDispatcher { contract_address: modules_storage_path.at(i).read() }
.module_burn_action(from, amount);
};
}

fn can_transfer(
self: @ContractState, from: ContractAddress, to: ContractAddress, amount: u256,
) -> bool {
let mut can_transfer = true;
let modules_storage_path = self.modules.deref();
for i in 0..modules_storage_path.len() {
let check_result = IModuleDispatcher {
contract_address: modules_storage_path.at(i).read(),
}
.module_check(from, to, amount, starknet::get_contract_address());
if !check_result {
can_transfer = false;
break;
}
};
can_transfer
}

fn get_modules(self: @ContractState) -> Array<ContractAddress> {
self.modules.deref().into()
}

fn get_token_bound(self: @ContractState) -> ContractAddress {
self.token_bound.read()
}

fn is_module_bound(self: @ContractState, module: ContractAddress) -> bool {
self.module_bound.entry(module).read()
}
}

#[generate_trait]
impl InternalImpl of InternalTrait {
fn assert_only_token(self: @ContractState) {
assert!(
starknet::get_caller_address() == self.token_bound.read(),
"This address is not a token bound to the compliance contract",
);
}
}
}

0 comments on commit 6b5875e

Please sign in to comment.