Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/resolving #10

Merged
merged 11 commits into from
Oct 28, 2023
Prev Previous commit
Next Next commit
feat: extract internal to multiple files
  • Loading branch information
Th0rgal committed Oct 9, 2023

Unverified

This user has not yet uploaded their public signing key.
commit 9d6f6ecab3e4eb7aecdebc05845241df5bf398db
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