Skip to content

Commit

Permalink
Merge pull request #36 from starknet-id/feat/ar_discount_renew
Browse files Browse the repository at this point in the history
Feat/ar discount renew
  • Loading branch information
Th0rgal authored May 6, 2024
2 parents 2fa1c34 + 05d4c13 commit 02d547b
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ mod naming;
mod resolver;
mod pricing;
mod referral;
mod auto_renewal;
13 changes: 13 additions & 0 deletions src/interface/auto_renewal.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use starknet::ContractAddress;

#[starknet::interface]
trait IAutoRenewal<TContractState> {
fn get_renewing_allowance(
self: @TContractState, domain: felt252, renewer: starknet::ContractAddress,
) -> u256;

// naming, erc20, tax
fn get_contracts(
self: @TContractState
) -> (starknet::ContractAddress, starknet::ContractAddress, starknet::ContractAddress);
}
9 changes: 8 additions & 1 deletion src/interface/naming.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ trait INaming<TContractState> {
self: @TContractState, domain: Span<felt252>, hint: Span<felt252>
) -> ContractAddress;

fn address_to_domain(self: @TContractState, address: ContractAddress, hint: Span<felt252>) -> Span<felt252>;
fn address_to_domain(
self: @TContractState, address: ContractAddress, hint: Span<felt252>
) -> Span<felt252>;

// external
fn buy(
Expand Down Expand Up @@ -70,6 +72,8 @@ trait INaming<TContractState> {
sig: (felt252, felt252),
);

fn ar_discount_renew(ref self: TContractState, domain: felt252, ar_contract: ContractAddress,);

fn auto_renew_altcoin(
ref self: TContractState,
domain: felt252,
Expand Down Expand Up @@ -117,4 +121,7 @@ trait INaming<TContractState> {
fn whitelist_renewal_contract(ref self: TContractState, contract: ContractAddress);

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

fn toggle_ar_discount_renew(ref self: TContractState);

}
57 changes: 57 additions & 0 deletions src/naming/main.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod Naming {
resolver::{IResolver, IResolverDispatcher, IResolverDispatcherTrait},
pricing::{IPricing, IPricingDispatcher, IPricingDispatcherTrait},
referral::{IReferral, IReferralDispatcher, IReferralDispatcherTrait},
auto_renewal::{IAutoRenewal, IAutoRenewalDispatcher, IAutoRenewalDispatcherTrait}
}
};
use clone::Clone;
Expand Down Expand Up @@ -136,6 +137,9 @@ mod Naming {
_address_to_domain: LegacyMap<(ContractAddress, usize), felt252>,
_server_pub_key: felt252,
_whitelisted_renewal_contracts: LegacyMap<ContractAddress, bool>,
// a for alpha, as we will probably do this campaign again in the future
_ar_discount_blacklist_a: LegacyMap<felt252, bool>,
_ar_discount_renew_enabled: bool,
#[substorage(v0)]
storage_read: storage_read_component::Storage,
}
Expand Down Expand Up @@ -463,6 +467,53 @@ mod Naming {
self.emit(Event::DomainRenewal(DomainRenewal { domain, new_expiry }));
}

fn ar_discount_renew(
ref self: ContractState, domain: felt252, ar_contract: ContractAddress,
) {
// First we check the discount is enabled
assert(self._ar_discount_renew_enabled.read(), 'Discount disabled');

// We check that domain didn't already claim the discount
assert(!self._ar_discount_blacklist_a.read(domain), 'You can\'t claim this twice');

// We check it's a valid AR contract, then we check that AR is enabled,
// we don't validate the pricing because it could change
assert(self._whitelisted_renewal_contracts.read(ar_contract), 'AR not whitelisted');
let auto_renewal_dispatcher = IAutoRenewalDispatcher { contract_address: ar_contract };
let caller = get_caller_address();
let ar_allowance = auto_renewal_dispatcher.get_renewing_allowance(domain, caller);
assert(ar_allowance != 0, 'Invalid AR allowance');
let (_, erc20, _) = auto_renewal_dispatcher.get_contracts();
let erc20_allowance = IERC20CamelDispatcher { contract_address: erc20 }
.allowance(caller, ar_contract);
assert(erc20_allowance != 0, 'Invalid ERC20 allowance');

// We then blacklist that domain for this discount
self._ar_discount_blacklist_a.write(domain, true);

// We can finally renew the domain with no SaleMetadata event since it's free
let now = get_block_timestamp();
let hashed_domain = self.hash_domain(array![domain].span());
let domain_data = self._domain_data.read(hashed_domain);
// we extended its expiry by 90 days (~3 months)
let new_expiry = if domain_data.expiry <= now {
now + 86400 * 90
} else {
domain_data.expiry + 86400 * 90
};

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,
Expand Down Expand Up @@ -721,6 +772,12 @@ mod Naming {
assert(get_caller_address() == self._admin_address.read(), 'you are not admin');
self._whitelisted_renewal_contracts.write(contract, false);
}


fn toggle_ar_discount_renew(ref self: ContractState) {
assert(get_caller_address() == self._admin_address.read(), 'you are not admin');
self._ar_discount_renew_enabled.write(!self._ar_discount_renew_enabled.read());
}
}
}

1 change: 1 addition & 0 deletions src/tests/naming.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ mod test_custom_resolver;
mod test_usecases;
mod test_features;
mod test_altcoin;
mod test_ar_discount;
194 changes: 194 additions & 0 deletions src/tests/naming/test_ar_discount.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
use array::ArrayTrait;
use array::SpanTrait;
use option::OptionTrait;
use zeroable::Zeroable;
use traits::Into;
use starknet::testing;
use starknet::ContractAddress;
use starknet::contract_address::ContractAddressZeroable;
use starknet::contract_address_const;
use starknet::testing::set_contract_address;
use super::super::utils;
use openzeppelin::token::erc20::{
interface::{IERC20Camel, IERC20CamelDispatcher, IERC20CamelDispatcherTrait}
};
use identity::{
identity::main::Identity, interface::identity::{IIdentityDispatcher, IIdentityDispatcherTrait}
};
use naming::interface::naming::{INamingDispatcher, INamingDispatcherTrait};
use naming::interface::pricing::{IPricingDispatcher, IPricingDispatcherTrait};
use naming::interface::auto_renewal::{
IAutoRenewal, IAutoRenewalDispatcher, IAutoRenewalDispatcherTrait
};
use naming::naming::main::Naming;
use naming::pricing::Pricing;
use super::common::deploy;
use naming::naming::main::Naming::Discount;


#[starknet::contract]
mod DummyAutoRenewal {
use core::array::ArrayTrait;
use starknet::ContractAddress;
use starknet::{contract_address_const, get_caller_address, get_contract_address};

#[storage]
struct Storage {
erc20: starknet::ContractAddress,
}

#[constructor]
fn constructor(ref self: ContractState, erc20: starknet::ContractAddress) {
self.erc20.write(erc20);
}

#[abi(embed_v0)]
impl DummyImpl of naming::interface::auto_renewal::IAutoRenewal<ContractState> {
fn get_renewing_allowance(
self: @ContractState, domain: felt252, renewer: starknet::ContractAddress,
) -> u256 {
1
}

// naming, erc20, tax
fn get_contracts(
self: @ContractState
) -> (starknet::ContractAddress, starknet::ContractAddress, starknet::ContractAddress) {
(contract_address_const::<0x0>(), self.erc20.read(), contract_address_const::<0x0>())
}
}
}

#[test]
#[available_gas(2000000000)]
fn test_ar_discount() {
// setup
let (eth, pricing, identity, naming) = deploy();
let caller = contract_address_const::<0x123>();
set_contract_address(caller);
let id: u128 = 1;
let th0rgal: felt252 = 33133781693;

//we mint an id
identity.mint(id);

// we check how much a domain costs
let (_, price) = pricing.compute_buy_price(7, 365);

// we allow the naming to take our money
eth.approve(naming.contract_address, price);

// we buy with no resolver, no sponsor, no discount and empty metadata
naming
.buy(
id, th0rgal, 365, ContractAddressZeroable::zero(), ContractAddressZeroable::zero(), 0, 0
);

let auto_renewal = utils::deploy(
DummyAutoRenewal::TEST_CLASS_HASH, array![eth.contract_address.into()]
);

let current_expiry = naming.domain_to_expiry(array![th0rgal].span());

// we set the renewal contract and enable the discount
naming.whitelist_renewal_contract(auto_renewal);
naming.toggle_ar_discount_renew();
let (_, yearly_renewal_price) = pricing.compute_renew_price(7, 365);
eth.approve(auto_renewal, yearly_renewal_price);
let _allowance = eth.allowance(caller, auto_renewal);
naming.ar_discount_renew(th0rgal, auto_renewal);

// we don't set the auto renewal allowance in this test because we
// use a dummy contract which always return 1, theoretically we should
// set it to infinity (2**256-1)
let new_expiry = naming.domain_to_expiry(array![th0rgal].span());
assert(new_expiry - current_expiry == 86400 * 90, 'Invalid expiry');
}


#[test]
#[available_gas(2000000000)]
#[should_panic(expected: ('Discount disabled', 'ENTRYPOINT_FAILED'))]
fn test_ar_discount_not_enabled() {
// setup
let (eth, pricing, identity, naming) = deploy();
let caller = contract_address_const::<0x123>();
set_contract_address(caller);
let id: u128 = 1;
let th0rgal: felt252 = 33133781693;

//we mint an id
identity.mint(id);

// we check how much a domain costs
let (_, price) = pricing.compute_buy_price(7, 365);

// we allow the naming to take our money
eth.approve(naming.contract_address, price);

// we buy with no resolver, no sponsor, no discount and empty metadata
naming
.buy(
id, th0rgal, 365, ContractAddressZeroable::zero(), ContractAddressZeroable::zero(), 0, 0
);

let auto_renewal = utils::deploy(
DummyAutoRenewal::TEST_CLASS_HASH, array![eth.contract_address.into()]
);

// we set the renewal contract and don't enable the discount
naming.whitelist_renewal_contract(auto_renewal);
//naming.toggle_ar_discount_renew();
let (_, yearly_renewal_price) = pricing.compute_renew_price(7, 365);
eth.approve(auto_renewal, yearly_renewal_price);
let _allowance = eth.allowance(caller, auto_renewal);
naming.ar_discount_renew(th0rgal, auto_renewal);
}


#[test]
#[available_gas(2000000000)]
#[should_panic(expected: ('Invalid ERC20 allowance', 'ENTRYPOINT_FAILED'))]
fn test_ar_discount_wrong_ar_allowance() {
// setup
let (eth, pricing, identity, naming) = deploy();
let caller = contract_address_const::<0x123>();
set_contract_address(caller);
let id: u128 = 1;
let th0rgal: felt252 = 33133781693;

//we mint an id
identity.mint(id);

// we check how much a domain costs
let (_, price) = pricing.compute_buy_price(7, 365);

// we allow the naming to take our money
eth.approve(naming.contract_address, price);

// we buy with no resolver, no sponsor, no discount and empty metadata
naming
.buy(
id, th0rgal, 365, ContractAddressZeroable::zero(), ContractAddressZeroable::zero(), 0, 0
);

let auto_renewal = utils::deploy(
DummyAutoRenewal::TEST_CLASS_HASH, array![eth.contract_address.into()]
);

let current_expiry = naming.domain_to_expiry(array![th0rgal].span());

// we set the renewal contract and enable the discount
naming.whitelist_renewal_contract(auto_renewal);
naming.toggle_ar_discount_renew();
let (_, _yearly_renewal_price) = pricing.compute_renew_price(7, 365);
//eth.approve(auto_renewal, yearly_renewal_price);
let _allowance = eth.allowance(caller, auto_renewal);
naming.ar_discount_renew(th0rgal, auto_renewal);

// we don't set the auto renewal allowance in this test because we
// use a dummy contract which always return 1, theoretically we should
// set it to infinity (2**256-1)
let new_expiry = naming.domain_to_expiry(array![th0rgal].span());
assert(new_expiry - current_expiry == 86400 * 90, 'Invalid expiry');
}

0 comments on commit 02d547b

Please sign in to comment.