diff --git a/src/interface/resolver.cairo b/src/interface/resolver.cairo index 327cbee..d9a310b 100644 --- a/src/interface/resolver.cairo +++ b/src/interface/resolver.cairo @@ -5,5 +5,8 @@ trait IResolver { fn resolve( self: @TContractState, domain: Span, field: felt252, hint: Span ) -> felt252; - fn update_uri(ref self: TContractState, new_uri: Span); + + fn get_uris(self: @TContractState) -> Array; + fn add_uri(ref self: TContractState, new_uri: Span); + fn remove_uri(ref self: TContractState, index: felt252); } diff --git a/src/resolver.cairo b/src/resolver.cairo index b396d4f..38c9602 100644 --- a/src/resolver.cairo +++ b/src/resolver.cairo @@ -21,7 +21,7 @@ mod Resolver { #[storage] struct Storage { public_key: felt252, - uri: LegacyMap, + uri: LegacyMap<(felt252, felt252), felt252>, #[substorage(v0)] storage_read: storage_read_component::Storage, #[substorage(v0)] @@ -40,18 +40,15 @@ mod Resolver { #[derive(Drop, starknet::Event)] struct StarknetIDOffChainResolverUpdate { - uri: Span, + uri_added: Span, + uri_removed: Span, } #[constructor] - fn constructor( - ref self: ContractState, owner: ContractAddress, _public_key: felt252, uri: Span - ) { + fn constructor(ref self: ContractState, owner: ContractAddress, _public_key: felt252) { self.ownable.initializer(owner); self.public_key.write(_public_key); - self.store_uri(uri); - self.emit(StarknetIDOffChainResolverUpdate { uri }); } #[external(v0)] @@ -60,7 +57,7 @@ mod Resolver { self: @ContractState, domain: Span, field: felt252, hint: Span ) -> felt252 { if hint.len() != 4 { - panic(self.get_uri(array!['offchain_resolving'])); + panic(self.get_error_array(array!['offchain_resolving'], domain)); } let max_validity = *hint.at(3); @@ -86,37 +83,118 @@ mod Resolver { return *hint.at(0); } - fn update_uri(ref self: ContractState, new_uri: Span) { + fn get_uris(self: @ContractState) -> Array { + let mut res: Array = array![]; + let mut i: felt252 = 0; + loop { + if self.uri.read((i, 0)) == 0 { + break; + } + if self.uri.read((i, 0)) != 'this call was removed' { + // we get the next uri at index i + let mut new_uri = self.get_uri_at_index(i); + res.append(new_uri.len().into()); + loop { + match new_uri.pop_front() { + Option::Some(value) => { res.append(value); }, + Option::None => { break; } + } + }; + } + i += 1; + }; + res + } + + fn add_uri(ref self: ContractState, new_uri: Span) { self.ownable.assert_only_owner(); - self.store_uri(new_uri); - self.emit(StarknetIDOffChainResolverUpdate { uri: new_uri, }); + let mut i: felt252 = 0; + loop { + if self.uri.read((i, 0)) == 0 { + self + .emit( + StarknetIDOffChainResolverUpdate { + uri_added: new_uri, uri_removed: array![].span() + } + ); + self.store_uri(new_uri, i); + + break; + } + i += 1; + }; + } + + fn remove_uri(ref self: ContractState, index: felt252) { + self.ownable.assert_only_owner(); + let uri_removed = self.get_uri_at_index(index).span(); + self.emit(StarknetIDOffChainResolverUpdate { uri_added: array![].span(), uri_removed }); + self.uri.write((index, 0), 'this call was removed'); } } #[generate_trait] impl InternalImpl of InternalTrait { - fn store_uri(ref self: ContractState, mut uri: Span) { - let mut index = 0; + fn get_uri_at_index(self: @ContractState, index: felt252) -> Array { + let mut res = array![]; + let mut j: felt252 = 0; + loop { + let value = self.uri.read((index, j)); + if value == 0 { + break; + } + res.append(value); + j += 1; + }; + res + } + + fn store_uri(ref self: ContractState, mut uri: Span, index: felt252) { + let mut j = 0; loop { match uri.pop_front() { Option::Some(value) => { - self.uri.write(index, *value); - index += 1; + self.uri.write((index, j), *value); + j += 1; }, Option::None => { break; } } }; } - fn get_uri(self: @ContractState, mut res: Array) -> Array { - let mut index = 0; + fn get_error_array( + self: @ContractState, mut res: Array, mut domain: Span + ) -> Array { + // append domain to error array + res.append(domain.len().into()); loop { - let value = self.uri.read(index); - if value == 0 { + match domain.pop_front() { + Option::Some(value) => { res.append(*value); }, + Option::None => { break; } + } + }; + // append uris to error array + self.append_uris(res) + } + + fn append_uris(self: @ContractState, mut res: Array) -> Array { + let mut i: felt252 = 0; + loop { + if self.uri.read((i, 0)) == 0 { break; } - res.append(value); - index += 1; + if self.uri.read((i, 0)) != 'this call was removed' { + // we get the next uri at index i + let mut new_uri = self.get_uri_at_index(i); + res.append(new_uri.len().into()); + loop { + match new_uri.pop_front() { + Option::Some(value) => { res.append(value); }, + Option::None => { break; } + } + }; + } + i += 1; }; res } diff --git a/src/tests/resolver/test_resolver.cairo b/src/tests/resolver/test_resolver.cairo index c3707c2..948c31a 100644 --- a/src/tests/resolver/test_resolver.cairo +++ b/src/tests/resolver/test_resolver.cairo @@ -1,5 +1,6 @@ use array::ArrayTrait; use debug::PrintTrait; +use core::option::OptionTrait; use zeroable::Zeroable; use traits::Into; @@ -48,9 +49,7 @@ fn deploy( Naming::TEST_CLASS_HASH, array![identity.into(), pricing.into(), 0, admin] ); - let resolver = utils::deploy( - Resolver::TEST_CLASS_HASH, array![admin, pub_key, 1, 'http://0.0.0.0:8090'] - ); + let resolver = utils::deploy(Resolver::TEST_CLASS_HASH, array![admin, pub_key]); ( IERC20CamelDispatcher { contract_address: eth }, IPricingDispatcher { contract_address: pricing }, @@ -60,11 +59,59 @@ fn deploy( ) } +#[test] +#[available_gas(20000000000)] +fn test_uri() { + let (eth, pricing, identity, naming, resolver) = deploy( + 0x64018d8ea7829641419aff38ea79efd3eafedf3a5c1fe001d35339b889d48f4 + ); + + let caller = contract_address_const::<0x123>(); + set_contract_address(caller); + + // add a some uris + resolver.add_uri(array!['http://0.0.0.0:8090/resolve?dom', 'ain='].span()); + resolver.add_uri(array!['http://sepolia.starknet.id/reso', 'lve?domain='].span()); + resolver.add_uri(array!['http://sepolia_2.starknet.id/re', 'solve?domain='].span()); + + let mut uris = resolver.get_uris(); + assert(uris.len() == 9, 'wrong length'); + assert(uris.at(0) == @2, 'wrong nb of arg'); + assert(uris.at(1) == @'http://0.0.0.0:8090/resolve?dom', 'wrong uri'); + assert(uris.at(2) == @'ain=', 'wrong uri'); + assert(uris.at(3) == @2, 'wrong nb of arg'); + assert(uris.at(4) == @'http://sepolia.starknet.id/reso', 'wrong 2nd uri'); + assert(uris.at(5) == @'lve?domain=', 'wrong 2nd uri'); + assert(uris.at(6) == @2, 'wrong nb of arg'); + assert(uris.at(7) == @'http://sepolia_2.starknet.id/re', 'wrong 2nd uri'); + assert(uris.at(8) == @'solve?domain=', 'wrong 2nd uri'); + + // remove the uri at index 1 + resolver.remove_uri(1); + let mut uris = resolver.get_uris(); + assert(uris.len() == 6, 'wrong length'); + assert(uris.at(0) == @2, 'wrong nb of arg'); + assert(uris.at(1) == @'http://0.0.0.0:8090/resolve?dom', 'wrong uri'); + assert(uris.at(2) == @'ain=', 'wrong uri'); + assert(uris.at(3) == @2, 'wrong nb of arg'); + assert(uris.at(4) == @'http://sepolia_2.starknet.id/re', 'wrong 2nd uri'); + assert(uris.at(5) == @'solve?domain=', 'wrong 2nd uri'); +} + #[test] #[available_gas(20000000000)] #[should_panic( expected: ( - 'offchain_resolving', 'http://0.0.0.0:8090', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED' + 'offchain_resolving', + 1, + 999902, + 1, + 'http://0.0.0.0:8090', + 2, + 'http://0.0.0.0:8090/resolve?dom', + 'ain=', + 'ENTRYPOINT_FAILED', + 'ENTRYPOINT_FAILED' ) )] fn test_offchain_resolving_no_hint() { @@ -78,6 +125,10 @@ fn test_offchain_resolving_no_hint() { let iris: felt252 = 999902; let notion: felt252 = 1059716045; + // add uri + resolver.add_uri(array!['http://0.0.0.0:8090'].span()); + resolver.add_uri(array!['http://0.0.0.0:8090/resolve?dom', 'ain='].span()); + //we mint an identity identity.mint(id1); @@ -109,6 +160,10 @@ fn test_offchain_resolving_with_hint() { let notion: felt252 = 1059716045; let iris: felt252 = 999902; + // add uri + resolver.add_uri(array!['http://0.0.0.0:8090'].span()); + resolver.add_uri(array!['http://0.0.0.0:8090/resolve?dom', 'ain='].span()); + let max_validity: felt252 = 1701167467; let timestamp: u64 = 1701167467 - 1800; // max_validity - 30 minutes set_block_timestamp(timestamp);