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

feat: add altcoin buy & renew support #22

Merged
merged 10 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could drop these two expiry check because the auto renew contract already ensures you can't do something abusive (your domain should expire soon and we can renew only for one year)

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
Loading