From af6ed6dd0541912d5c04d0970191b5dbf092efad Mon Sep 17 00:00:00 2001
From: Thomas Marchand <thomas.marchand@tuta.io>
Date: Sat, 13 Jul 2024 16:48:44 +0100
Subject: [PATCH 1/2] fix: crash on reverse resolving post subdomain transfer

---
 src/naming/main.cairo              |  5 +++
 src/tests/naming/test_abuses.cairo | 51 ++++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+)

diff --git a/src/naming/main.cairo b/src/naming/main.cairo
index 8e1e9ed..e1dc5c8 100644
--- a/src/naming/main.cairo
+++ b/src/naming/main.cairo
@@ -203,6 +203,9 @@ mod Naming {
         fn domain_to_address(
             self: @ContractState, domain: Span<felt252>, hint: Span<felt252>
         ) -> ContractAddress {
+            if domain.len() == 0 {
+                return ContractAddressZeroable::zero();
+            }
             let (resolver, parent_length) = self.domain_to_resolver(domain);
             // if there is a resolver starting from the top
             if (resolver != ContractAddressZeroable::zero()) {
@@ -305,9 +308,11 @@ mod Naming {
                 //     self.domain_to_address(domain, array![].span()) == address,
                 //     'domain not pointing back'
                 // );
+                println!("echo: {:?}", domain);
                 if self.domain_to_address(domain, array![].span()) != address {
                     return array![].span();
                 }
+                println!("hey");
                 domain
             }
         }
diff --git a/src/tests/naming/test_abuses.cairo b/src/tests/naming/test_abuses.cairo
index ef0a32c..3fa0930 100644
--- a/src/tests/naming/test_abuses.cairo
+++ b/src/tests/naming/test_abuses.cairo
@@ -409,3 +409,54 @@ fn test_buy_empty_domain() {
             0
         );
 }
+
+
+#[test]
+#[available_gas(2000000000)]
+fn test_subdomain_reverse() {
+    // setup
+    let (eth, pricing, identity, naming) = deploy();
+    let alpha = contract_address_const::<0x123>();
+    let bravo = contract_address_const::<0x456>();
+    let charlie = contract_address_const::<0x789>();
+
+    // we mint the ids
+    set_contract_address(alpha);
+    identity.mint(1);
+    set_contract_address(bravo);
+    identity.mint(2);
+    set_contract_address(charlie);
+    identity.mint(3);
+
+    set_contract_address(alpha);
+    let aller: felt252 = 35683102;
+
+    // we check how much a domain costs
+    let (_, price) = pricing.compute_buy_price(5, 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(1, aller, 365, ContractAddressZeroable::zero(), ContractAddressZeroable::zero(), 0, 0);
+
+    let subdomain = array![aller, aller].span();
+
+    // we transfer aller.aller.stark to id2
+    naming.transfer_domain(subdomain, 2);
+
+    // and make sure the owner has been updated
+    assert(naming.domain_to_id(subdomain) == 2, 'owner not updated correctly');
+    set_contract_address(bravo);
+    let result = naming.address_to_domain(bravo, array![].span());
+    assert(result == array![].span(), 'unexpected result');
+    // we then set this subdomain as main domain and ensures reverse resolving works
+    identity.set_main_id(2);
+    let result = naming.address_to_domain(bravo, array![].span());
+    assert(result == subdomain, 'unexpected result');
+    // before transfering this subdomain
+    naming.transfer_domain(subdomain, 3);
+    let result = naming.address_to_domain(bravo, array![].span());
+    assert(result == array![].span(), 'unexpected result');
+}

From b23ed6ce780aa3d86e9f42da41c40cd29c37876f Mon Sep 17 00:00:00 2001
From: Thomas Marchand <thomas.marchand@tuta.io>
Date: Sat, 13 Jul 2024 17:13:48 +0100
Subject: [PATCH 2/2] feat: add revoke_domain and tests

---
 src/interface/naming.cairo         |  2 +
 src/naming/main.cairo              | 45 ++++++++++++++++++-
 src/tests/naming/test_abuses.cairo | 72 ++++++++++++++++++++++++++++++
 3 files changed, 117 insertions(+), 2 deletions(-)

diff --git a/src/interface/naming.cairo b/src/interface/naming.cairo
index e5caf55..c94165e 100644
--- a/src/interface/naming.cairo
+++ b/src/interface/naming.cairo
@@ -89,6 +89,8 @@ trait INaming<TContractState> {
 
     fn reset_subdomains(ref self: TContractState, domain: Span<felt252>);
 
+    fn revoke_domain(ref self: TContractState, domain: Span<felt252>);
+
     fn set_address_to_domain(ref self: TContractState, domain: Span<felt252>, hint: Span<felt252>);
 
     fn clear_legacy_domain_to_address(ref self: TContractState, domain: Span<felt252>);
diff --git a/src/naming/main.cairo b/src/naming/main.cairo
index e1dc5c8..b70507f 100644
--- a/src/naming/main.cairo
+++ b/src/naming/main.cairo
@@ -308,11 +308,9 @@ mod Naming {
                 //     self.domain_to_address(domain, array![].span()) == address,
                 //     'domain not pointing back'
                 // );
-                println!("echo: {:?}", domain);
                 if self.domain_to_address(domain, array![].span()) != address {
                     return array![].span();
                 }
-                println!("hey");
                 domain
             }
         }
@@ -682,6 +680,49 @@ mod Naming {
             self.emit(Event::SubdomainsReset(SubdomainsReset { domain: domain, }));
         }
 
+        // this function will reset the native resolving of a domain,
+        // it is intended to be called by the owner of a parent domain
+        // who would like to take back a specific subdomain and its 
+        // recursive subdomains but could also be used as a way to burn
+        // a root domain
+        fn revoke_domain(ref self: ContractState, domain: Span<felt252>) {
+            self.assert_control_domain(domain, get_caller_address());
+            let hashed_domain = self.hash_domain(domain);
+            let current_domain_data = self._domain_data.read(hashed_domain);
+            let new_domain_data = DomainData {
+                owner: 0,
+                resolver: ContractAddressZeroable::zero(),
+                address: ContractAddressZeroable::zero(),
+                expiry: current_domain_data.expiry,
+                key: current_domain_data.key + 1,
+                parent_key: current_domain_data.parent_key,
+            };
+            self._domain_data.write(hashed_domain, new_domain_data);
+            if current_domain_data.resolver != new_domain_data.resolver {
+                self
+                    .emit(
+                        Event::DomainResolverUpdate(
+                            DomainResolverUpdate {
+                                domain, resolver: ContractAddressZeroable::zero()
+                            }
+                        )
+                    );
+            }
+            if current_domain_data.address != new_domain_data.address {
+                self.emit(Event::LegacyDomainToAddressClear(LegacyDomainToAddressClear { domain }));
+            }
+            self.emit(Event::SubdomainsReset(SubdomainsReset { domain: domain, }));
+            self
+                .emit(
+                    Event::DomainTransfer(
+                        DomainTransfer {
+                            domain: domain,
+                            prev_owner: current_domain_data.owner,
+                            new_owner: new_domain_data.owner
+                        }
+                    )
+                );
+        }
 
         // will override your main id
         fn set_address_to_domain(
diff --git a/src/tests/naming/test_abuses.cairo b/src/tests/naming/test_abuses.cairo
index 3fa0930..d0746b2 100644
--- a/src/tests/naming/test_abuses.cairo
+++ b/src/tests/naming/test_abuses.cairo
@@ -460,3 +460,75 @@ fn test_subdomain_reverse() {
     let result = naming.address_to_domain(bravo, array![].span());
     assert(result == array![].span(), 'unexpected result');
 }
+
+
+#[test]
+#[available_gas(2000000000)]
+fn test_use_revoke_domain() {
+    // setup
+    let (eth, pricing, identity, naming) = deploy();
+    let alpha = contract_address_const::<0x123>();
+    let bravo = contract_address_const::<0x456>();
+
+    // we mint the ids
+
+    set_contract_address(alpha);
+    identity.mint(1);
+    set_contract_address(bravo);
+    identity.mint(2);
+
+    set_contract_address(alpha);
+    let aller: felt252 = 35683102;
+
+    // we check how much a domain costs
+    let (_, price) = pricing.compute_buy_price(5, 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(1, aller, 365, ContractAddressZeroable::zero(), ContractAddressZeroable::zero(), 0, 0);
+
+    let root_domain = array![aller].span();
+    let subdomain = array![aller, aller].span();
+
+    // we transfer aller.aller.stark to id2
+    naming.transfer_domain(subdomain, 2);
+
+    // and make sure the owner has been updated
+    assert(naming.domain_to_id(subdomain) == 2, 'owner not updated correctly');
+
+    // now bravo should be able to create a subsubdomain (charlie.aller.aller.stark):
+    set_contract_address(bravo);
+    let subsubdomain = array!['charlie', aller, aller].span();
+    naming.transfer_domain(subsubdomain, 3);
+
+    // alpha resets subdomains of aller.stark
+    set_contract_address(alpha);
+    naming.revoke_domain(subdomain);
+
+    // ensure aller.stark still resolves
+    assert(naming.domain_to_id(root_domain) == 1, 'owner not updated correctly');
+    // ensure the subdomain was reset
+    assert(naming.domain_to_id(subdomain) == 0, 'target not updated correctly');
+    assert(
+        naming.domain_to_address(subdomain, array![].span()) == ContractAddressZeroable::zero(),
+        'owner not updated correctly'
+    );
+    assert(
+        naming.domain_to_data(subdomain).resolver == ContractAddressZeroable::zero(),
+        'resolver not updated correctly'
+    );
+
+    // ensure subsubdomain too
+    assert(naming.domain_to_id(subsubdomain) == 0, 'owner not updated correctly');
+    assert(
+        naming.domain_to_address(subsubdomain, array![].span()) == ContractAddressZeroable::zero(),
+        'target not updated correctly'
+    );
+    assert(
+        naming.domain_to_data(subsubdomain).resolver == ContractAddressZeroable::zero(),
+        'resolver not updated correctly'
+    );
+}