Skip to content

Commit

Permalink
Merge pull request #22 from starknet-id/feat/strk_support
Browse files Browse the repository at this point in the history
feat: add altcoin buy & renew support
  • Loading branch information
Th0rgal authored Mar 8, 2024
2 parents 6e45f7f + 66c9e1a commit 6a922ce
Show file tree
Hide file tree
Showing 10 changed files with 720 additions and 10 deletions.
1 change: 1 addition & 0 deletions Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ starknet = "2.3.1"
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", rev = "f3e2a5f0547a429c716f32471b06df729cbdfb9f" }
storage_read = { git = "https://github.com/starknet-id/storage_read_component.git", rev = "c6c69e15d34abfc39ac51dc21b96724e2e19ff31" }
identity = { git = "https://github.com/starknet-id/identity.git", rev = "eb37846330b78e1410cf5e3e17a5dcca5652f921" }
wadray = { git = "https://github.com/lindy-labs/wadray.git", rev = "ea1fa019e7c7c60878ac1e613bc81693524436f5" }

[[target.starknet-contract]]
# Enable Sierra codegen.
Expand Down
17 changes: 17 additions & 0 deletions scripts/generate_sig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
from starkware.crypto.signature.signature import private_to_stark_key, get_random_private_key, sign
from starknet_py.hash.utils import pedersen_hash

priv_key = 123
pub_key = private_to_stark_key(priv_key)
print("pub_key:", hex(pub_key))

user_addr = 0x123
erc20_addr = 0x5
quote = 591205338160899000000
max_validity = 1000
encoded_string = 724720344857006587549020016926517802128122613457935427138661
data = pedersen_hash(pedersen_hash(pedersen_hash(erc20_addr, quote), max_validity), encoded_string)

(x, y) = sign(data, priv_key)
print("sig:", hex(x), hex(y))
46 changes: 46 additions & 0 deletions src/interface/naming.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use starknet::{ContractAddress, ClassHash};
use naming::naming::main::Naming::{Discount, DomainData};
use wadray::Wad;

#[starknet::interface]
trait INaming<TContractState> {
Expand Down Expand Up @@ -32,6 +33,21 @@ trait INaming<TContractState> {
metadata: felt252,
);

fn altcoin_buy(
ref self: TContractState,
id: u128,
domain: felt252,
days: u16,
resolver: ContractAddress,
sponsor: ContractAddress,
discount_id: felt252,
metadata: felt252,
altcoin_addr: ContractAddress,
quote: Wad,
max_validity: u64,
sig: (felt252, felt252),
);

fn renew(
ref self: TContractState,
domain: felt252,
Expand All @@ -41,6 +57,30 @@ trait INaming<TContractState> {
metadata: felt252,
);

fn altcoin_renew(
ref self: TContractState,
domain: felt252,
days: u16,
sponsor: ContractAddress,
discount_id: felt252,
metadata: felt252,
altcoin_addr: ContractAddress,
quote: Wad,
max_validity: u64,
sig: (felt252, felt252),
);

fn auto_renew_altcoin(
ref self: TContractState,
domain: felt252,
days: u16,
sponsor: ContractAddress,
discount_id: felt252,
metadata: felt252,
altcoin_addr: ContractAddress,
price_in_altcoin: u256,
);

fn transfer_domain(ref self: TContractState, domain: Span<felt252>, target_id: u128);

fn reset_subdomains(ref self: TContractState, domain: Span<felt252>);
Expand Down Expand Up @@ -71,4 +111,10 @@ trait INaming<TContractState> {
fn set_referral_contract(ref self: TContractState, referral_contract: ContractAddress);

fn upgrade(ref self: TContractState, new_class_hash: ClassHash);

fn set_server_pub_key(ref self: TContractState, new_key: felt252);

fn whitelist_renewal_contract(ref self: TContractState, contract: ContractAddress);

fn blacklist_renewal_contract(ref self: TContractState, contract: ContractAddress);
}
2 changes: 1 addition & 1 deletion src/naming.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod main;
mod internal;
mod asserts;
mod utils;
mod utils;
204 changes: 204 additions & 0 deletions src/naming/main.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ mod Naming {
use starknet::class_hash::ClassHash;
use integer::{u256_safe_divmod, u256_as_non_zero};
use core::pedersen;
use hash::LegacyHash;
use ecdsa::check_ecdsa_signature;
use wadray::Wad;
use naming::{
naming::{asserts::AssertionsTrait, internal::InternalTrait, utils::UtilsTrait},
interface::{
Expand Down Expand Up @@ -131,6 +134,8 @@ mod Naming {
_domain_data: LegacyMap<felt252, DomainData>,
_hash_to_domain: LegacyMap<(felt252, usize), felt252>,
_address_to_domain: LegacyMap<(ContractAddress, usize), felt252>,
_server_pub_key: felt252,
_whitelisted_renewal_contracts: LegacyMap<ContractAddress, bool>,
#[substorage(v0)]
storage_read: storage_read_component::Storage,
}
Expand Down Expand Up @@ -272,6 +277,62 @@ mod Naming {
self.mint_domain(expiry, resolver, hashed_domain, id, domain);
}

fn altcoin_buy(
ref self: ContractState,
id: u128,
domain: felt252,
days: u16,
resolver: ContractAddress,
sponsor: ContractAddress,
discount_id: felt252,
metadata: felt252,
altcoin_addr: ContractAddress,
quote: Wad,
max_validity: u64,
sig: (felt252, felt252),
) {
let (hashed_domain, now, expiry) = self.assert_purchase_is_possible(id, domain, days);
// we need a u256 to be able to perform safe divisions
let domain_len = self.get_chars_len(domain.into());

// check quote timestamp is still valid
assert(get_block_timestamp() <= max_validity, 'quotation expired');

// verify signature
let altcoin: felt252 = altcoin_addr.into();
let quote_felt: felt252 = quote.into();
let message_hash = LegacyHash::hash(
LegacyHash::hash(LegacyHash::hash(altcoin, quote_felt), max_validity),
'starknet id altcoin quote'
);
let (sig0, sig1) = sig;
let is_valid = check_ecdsa_signature(
message_hash, self._server_pub_key.read(), sig0, sig1
);
assert(is_valid, 'Invalid signature');

// find domain cost in ETH
let (_, price_in_eth) = IPricingDispatcher {
contract_address: self._pricing_contract.read()
}
.compute_buy_price(domain_len, days);
// compute domain cost in altcoin
let price_in_altcoin = self
.get_altcoin_price(quote, price_in_eth.try_into().unwrap());
self
.pay_domain(
domain_len,
altcoin_addr,
price_in_altcoin,
now,
days,
domain,
sponsor,
discount_id
);
self.emit(Event::SaleMetadata(SaleMetadata { domain, metadata }));
self.mint_domain(expiry, resolver, hashed_domain, id, domain);
}

fn renew(
ref self: ContractState,
Expand Down Expand Up @@ -316,6 +377,134 @@ mod Naming {
self.emit(Event::DomainRenewal(DomainRenewal { domain, new_expiry }));
}

fn altcoin_renew(
ref self: ContractState,
domain: felt252,
days: u16,
sponsor: ContractAddress,
discount_id: felt252,
metadata: felt252,
altcoin_addr: ContractAddress,
quote: Wad,
max_validity: u64,
sig: (felt252, felt252),
) {
let now = get_block_timestamp();
let hashed_domain = self.hash_domain(array![domain].span());
let domain_data = self._domain_data.read(hashed_domain);

// check quote timestamp is still valid
assert(get_block_timestamp() <= max_validity, 'quotation expired');
// verify signature
let altcoin: felt252 = altcoin_addr.into();
let quote_felt: felt252 = quote.into();
let message_hash = LegacyHash::hash(
LegacyHash::hash(LegacyHash::hash(altcoin, quote_felt), max_validity),
'starknet id altcoin quote'
);
let (sig0, sig1) = sig;
let is_valid = check_ecdsa_signature(
message_hash, self._server_pub_key.read(), sig0, sig1
);
assert(is_valid, 'Invalid signature');

// we need a u256 to be able to perform safe divisions
let domain_len = self.get_chars_len(domain.into());
// find domain cost in ETH
let (_, price_in_eth) = IPricingDispatcher {
contract_address: self._pricing_contract.read()
}
.compute_renew_price(domain_len, days);
// compute domain cost in altcoin
let price_in_altcoin = self
.get_altcoin_price(quote, price_in_eth.try_into().unwrap());
self
.pay_domain(
domain_len,
altcoin_addr,
price_in_altcoin,
now,
days,
domain,
sponsor,
discount_id
);
self.emit(Event::SaleMetadata(SaleMetadata { domain, metadata }));
// find new domain expiry
let new_expiry = if domain_data.expiry <= now {
now + 86400 * days.into()
} else {
domain_data.expiry + 86400 * days.into()
};
// 25*365 = 9125
assert(new_expiry <= now + 86400 * 9125, 'purchase too long');
assert(days >= 6 * 30, 'purchase too short');

let data = DomainData {
owner: domain_data.owner,
resolver: domain_data.resolver,
address: domain_data.address,
expiry: new_expiry,
key: domain_data.key,
parent_key: 0,
};
self._domain_data.write(hashed_domain, data);
self.emit(Event::DomainRenewal(DomainRenewal { domain, new_expiry }));
}

fn auto_renew_altcoin(
ref self: ContractState,
domain: felt252,
days: u16,
sponsor: ContractAddress,
discount_id: felt252,
metadata: felt252,
altcoin_addr: ContractAddress,
price_in_altcoin: u256,
) {
let now = get_block_timestamp();
let hashed_domain = self.hash_domain(array![domain].span());
let domain_data = self._domain_data.read(hashed_domain);

// check caller is a whitelisted altcoin auto renewal contract
assert(
self._whitelisted_renewal_contracts.read(get_caller_address()),
'Caller not whitelisted'
);

// we need a u256 to be able to perform safe divisions
let domain_len = self.get_chars_len(domain.into());
self
.pay_domain(
domain_len,
altcoin_addr,
price_in_altcoin,
now,
days,
domain,
sponsor,
discount_id
);
self.emit(Event::SaleMetadata(SaleMetadata { domain, metadata }));
// find new domain expiry
let new_expiry = if domain_data.expiry <= now {
now + 86400 * days.into()
} else {
domain_data.expiry + 86400 * days.into()
};

let data = DomainData {
owner: domain_data.owner,
resolver: domain_data.resolver,
address: domain_data.address,
expiry: new_expiry,
key: domain_data.key,
parent_key: 0,
};
self._domain_data.write(hashed_domain, data);
self.emit(Event::DomainRenewal(DomainRenewal { domain, new_expiry }));
}

fn transfer_domain(ref self: ContractState, domain: Span<felt252>, target_id: u128) {
self.assert_control_domain(domain, get_caller_address());

Expand Down Expand Up @@ -507,6 +696,21 @@ mod Naming {
assert(!new_class_hash.is_zero(), 'Class hash cannot be zero');
starknet::replace_class_syscall(new_class_hash).unwrap();
}

fn set_server_pub_key(ref self: ContractState, new_key: felt252) {
assert(get_caller_address() == self._admin_address.read(), 'you are not admin');
self._server_pub_key.write(new_key);
}

fn whitelist_renewal_contract(ref self: ContractState, contract: ContractAddress) {
assert(get_caller_address() == self._admin_address.read(), 'you are not admin');
self._whitelisted_renewal_contracts.write(contract, true);
}

fn blacklist_renewal_contract(ref self: ContractState, contract: ContractAddress) {
assert(get_caller_address() == self._admin_address.read(), 'you are not admin');
self._whitelisted_renewal_contracts.write(contract, false);
}
}
}

7 changes: 7 additions & 0 deletions src/naming/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use naming::{
naming::main::{Naming, Naming::{_hash_to_domain, _hash_to_domainContractMemberStateTrait}}
};
use integer::{u256_safe_divmod, u256_as_non_zero};
use wadray::{Wad, WAD_SCALE};

#[generate_trait]
impl UtilsImpl of UtilsTrait {
Expand Down Expand Up @@ -57,4 +58,10 @@ impl UtilsImpl of UtilsTrait {
let next = self.get_chars_len(p);
1 + next
}

fn get_altcoin_price(
self: @Naming::ContractState, altcoin_quote: Wad, domain_price_eth: Wad
) -> u256 {
(domain_price_eth * altcoin_quote).into()
}
}
Loading

0 comments on commit 6a922ce

Please sign in to comment.