Skip to content

Commit

Permalink
feat: extract internal to multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
Th0rgal committed Oct 9, 2023
1 parent 28f7d17 commit 9d6f6ec
Show file tree
Hide file tree
Showing 6 changed files with 356 additions and 290 deletions.
3 changes: 3 additions & 0 deletions src/naming.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
mod main;
mod internal;
mod asserts;
mod utils;
107 changes: 107 additions & 0 deletions src/naming/asserts.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use naming::{
interface::{
naming::{INaming, INamingDispatcher, INamingDispatcherTrait},
resolver::{IResolver, IResolverDispatcher, IResolverDispatcherTrait},
pricing::{IPricing, IPricingDispatcher, IPricingDispatcherTrait},
referral::{IReferral, IReferralDispatcher, IReferralDispatcherTrait},
},
naming::main::{
Naming,
Naming::{
EventEmitter, _hash_to_domain, _hash_to_domainContractMemberStateTrait, _domain_data,
_domain_dataContractMemberStateTrait, starknetid_contract,
starknetid_contractContractMemberStateTrait, discounts,
discountsContractMemberStateTrait, _address_to_domain,
_address_to_domainContractMemberStateTrait, _referral_contract,
_referral_contractContractMemberStateTrait,
}
},
};
use identity::interface::identity::{IIdentity, IIdentityDispatcher, IIdentityDispatcherTrait};
use starknet::{
contract_address::ContractAddressZeroable, ContractAddress, get_caller_address,
get_contract_address, get_block_timestamp
};
use openzeppelin::token::erc20::interface::{
IERC20Camel, IERC20CamelDispatcher, IERC20CamelDispatcherTrait
};
use integer::{u256_safe_divmod, u256_as_non_zero};
use naming::naming::utils::UtilsTrait;


#[generate_trait]
impl AssertionsImpl of AssertionsTrait {
fn assert_purchase_is_possible(
self: @Naming::ContractState, identity: u128, domain: felt252, days: u16
) -> (felt252, u64, u64) {
let now = get_block_timestamp();

// Verify that the starknet.id doesn't already manage a domain
self.assert_id_availability(identity, now);

// Verify that the domain is not already taken or expired
let hashed_domain = self.hash_domain(array![domain].span());
let data = self._domain_data.read(hashed_domain);
assert(data.owner == 0 || data.expiry < now, 'unexpired domain');

// Verify expiration range
assert(days < 365 * 25, 'max purchase of 25 years');
assert(days > 2 * 30, 'min purchase of 2 month');
return (hashed_domain, now, now + 86400 * days.into());
}

fn assert_control_domain(
self: @Naming::ContractState, domain: Span<felt252>, account: ContractAddress
) {
// 1. account owns the domain
self.assert_is_owner(domain, account);
// 2. check domain expiration
let hashed_root_domain = self.hash_domain(domain.slice(domain.len() - 1, 1));
let root_domain_data = self._domain_data.read(hashed_root_domain);
assert(get_block_timestamp() <= root_domain_data.expiry, 'this domain has expired');
}

fn assert_is_owner(
self: @Naming::ContractState, domain: Span<felt252>, account: ContractAddress
) -> u32 {
let hashed_domain = self.hash_domain(domain);
let data = self._domain_data.read(hashed_domain);

// because erc721 crashes on zero
let owner = if data.owner == 0 {
ContractAddressZeroable::zero()
} else {
IIdentityDispatcher { contract_address: self.starknetid_contract.read() }
.owner_of(data.owner)
};

// if caller owns the starknet id, he owns the domain, we return the key
if owner == account {
return data.key;
};

// otherwise, if it is a root domain, he doesn't own it
assert(domain.len() != 1 && domain.len() != 0, 'you don\'t own this domain');

// if he doesn't own the starknet id, and doesn't own the domain, he might own the parent domain
let parent_key = self.assert_is_owner(domain.slice(1, domain.len() - 1), account);
// we ensure that the key is the same as the parent key
// this is to allow to revoke all subdomains in o(1) writes, by juste updating the key of the parent
if (data.parent_key != 0) {
assert(parent_key == data.parent_key, 'you no longer own this domain');
};
data.key
}

// this ensures a non expired domain is not already written on this identity
fn assert_id_availability(self: @Naming::ContractState, identity: u128, timestamp: u64) {
let id_hashed_domain = IIdentityDispatcher {
contract_address: self.starknetid_contract.read()
}
.get_verifier_data(identity, 'name', get_contract_address(), 0);
assert(
id_hashed_domain == 0 || self._domain_data.read(id_hashed_domain).expiry < timestamp,
'this id holds a domain'
);
}
}
185 changes: 185 additions & 0 deletions src/naming/internal.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use naming::{
interface::{
naming::{INaming, INamingDispatcher, INamingDispatcherTrait},
resolver::{IResolver, IResolverDispatcher, IResolverDispatcherTrait},
pricing::{IPricing, IPricingDispatcher, IPricingDispatcherTrait},
referral::{IReferral, IReferralDispatcher, IReferralDispatcherTrait},
},
naming::main::{
Naming,
Naming::{
EventEmitter, _hash_to_domain, _hash_to_domainContractMemberStateTrait, _domain_data,
_domain_dataContractMemberStateTrait, starknetid_contract,
starknetid_contractContractMemberStateTrait, discounts,
discountsContractMemberStateTrait, _address_to_domain,
_address_to_domainContractMemberStateTrait, _referral_contract,
_referral_contractContractMemberStateTrait,
}
}
};
use identity::interface::identity::{IIdentity, IIdentityDispatcher, IIdentityDispatcherTrait};
use starknet::{
contract_address::ContractAddressZeroable, ContractAddress, get_caller_address,
get_contract_address, get_block_timestamp
};
use openzeppelin::token::erc20::interface::{
IERC20Camel, IERC20CamelDispatcher, IERC20CamelDispatcherTrait
};
use naming::naming::utils::UtilsTrait;

#[generate_trait]
impl InternalImpl of InternalTrait {

fn read_address_to_domain(
self: @Naming::ContractState, address: ContractAddress, ref domain: Array<felt252>
) -> usize {
let subdomain = self._address_to_domain.read((address, domain.len()));
if subdomain == 0 {
domain.len()
} else {
domain.append(subdomain);
self.read_address_to_domain(address, ref domain)
}
}

fn set_address_to_domain_util(
ref self: Naming::ContractState, address: ContractAddress, mut domain: Span<felt252>
) {
match domain.pop_back() {
Option::Some(domain_part) => {
self._address_to_domain.write((address, domain.len()), *domain_part);
self.set_address_to_domain_util(address, domain)
},
Option::None => {}
}
}

fn domain_to_resolver(
self: @Naming::ContractState, domain: Span<felt252>, parent_start_id: u32
) -> (ContractAddress, u32) {
if parent_start_id == domain.len() {
return (ContractAddressZeroable::zero(), 0);
};

// hashing parent_domain
let hashed_domain = self
.hash_domain(domain.slice(parent_start_id, domain.len() - parent_start_id));

let domain_data = self._domain_data.read(hashed_domain);

if domain_data.resolver.into() != 0 {
return (domain_data.resolver, parent_start_id);
} else {
return self.domain_to_resolver(domain, parent_start_id + 1);
}
}

fn pay_domain(
self: @Naming::ContractState,
domain_len: usize,
erc20: ContractAddress,
price: u256,
now: u64,
days: u16,
domain: felt252,
sponsor: ContractAddress,
discount_id: felt252
) -> () {
// check the discount
let discounted_price = if (discount_id == 0) {
price
} else {
let discount = self.discounts.read(discount_id);
let (min, max) = discount.domain_len_range;
assert(min <= domain_len && domain_len <= max, 'invalid length for discount');

let (min, max) = discount.days_range;
assert(min <= days && days <= max, 'days out of discount range');

let (min, max) = discount.timestamp_range;
assert(min <= now && now <= max, 'time out of discount range');
// discount.amount won't overflow as it's a value chosen by the admin to be in range (0, 100)
(price * discount.amount) / 100
};

// pay the price
IERC20CamelDispatcher { contract_address: erc20 }
.transferFrom(get_caller_address(), get_contract_address(), discounted_price);
// add sponsor commission if eligible
if sponsor.into() != 0 {
IReferralDispatcher { contract_address: self._referral_contract.read() }
.add_commission(discounted_price, sponsor, sponsored_addr: get_caller_address());
}
}

fn mint_domain(
ref self: Naming::ContractState,
expiry: u64,
resolver: ContractAddress,
hashed_domain: felt252,
id: u128,
domain: felt252
) {
let data = Naming::DomainData {
owner: id,
resolver,
address: ContractAddressZeroable::zero(), // legacy native address
expiry,
key: 1,
parent_key: 0,
};
self._hash_to_domain.write((hashed_domain, 0), domain);
self._domain_data.write(hashed_domain, data);
self.emit(Naming::Event::DomainMint(Naming::DomainMint { domain, owner: id, expiry }));

IIdentityDispatcher { contract_address: self.starknetid_contract.read() }
.set_verifier_data(id, 'name', hashed_domain, 0);
if (resolver.into() != 0) {
self
.emit(
Naming::Event::DomainResolverUpdate(
Naming::DomainResolverUpdate { domain: array![domain].span(), resolver }
)
);
}
}

// returns domain_hash (or zero) and its value for a specific field
fn resolve_util(
self: @Naming::ContractState, domain: Span<felt252>, field: felt252
) -> (felt252, felt252) {
let (resolver, parent_start) = self.domain_to_resolver(domain, 0);
if (resolver != ContractAddressZeroable::zero()) {
(
0,
IResolverDispatcher { contract_address: resolver }
.resolve(domain.slice(parent_start, domain.len() - parent_start), field)
)
} else {
let hashed_domain = self.hash_domain(domain);
let domain_data = self._domain_data.read(hashed_domain);
// circuit breaker for root domain
(
hashed_domain,
if (domain.len() == 1) {
IIdentityDispatcher { contract_address: self.starknetid_contract.read() }
.get_crosschecked_user_data(domain_data.owner, field)
// handle reset subdomains
} else {
// todo: optimize by changing the hash definition from H(b, a) to H(a, b)
let parent_key = self
._domain_data
.read(self.hash_domain(domain.slice(1, domain.len() - 1)))
.key;

if parent_key == domain_data.parent_key {
IIdentityDispatcher { contract_address: self.starknetid_contract.read() }
.get_crosschecked_user_data(domain_data.owner, field)
} else {
0
}
}
)
}
}
}
Loading

0 comments on commit 9d6f6ec

Please sign in to comment.