-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
484 additions
and
286 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
Scarb.lock | ||
.env | ||
venv | ||
target | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
mod main; | ||
mod internal; | ||
mod asserts; | ||
mod utils; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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::{ | ||
ContractStateEventEmitter, _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' | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
use naming::{ | ||
interface::{ | ||
naming::{INaming, INamingDispatcher, INamingDispatcherTrait}, | ||
resolver::{IResolver, IResolverDispatcher, IResolverDispatcherTrait}, | ||
pricing::{IPricing, IPricingDispatcher, IPricingDispatcherTrait}, | ||
referral::{IReferral, IReferralDispatcher, IReferralDispatcherTrait}, | ||
}, | ||
naming::main::{ | ||
Naming, | ||
Naming::{ | ||
ContractStateEventEmitter, _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, hint: Span<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, hint) | ||
) | ||
} 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 | ||
} | ||
} | ||
) | ||
} | ||
} | ||
} |
Oops, something went wrong.