@@ -52,17 +52,15 @@ public sealed class NameService : Framework.SmartContract
5252 public static UInt160 OwnerOf ( ByteString tokenId )
5353 {
5454 StorageMap nameMap = new ( Storage . CurrentContext , Prefix_Name ) ;
55- NameState token = ( NameState ) StdLib . Deserialize ( nameMap [ GetKey ( tokenId ) ] ) ;
56- token . EnsureNotExpired ( ) ;
55+ NameState token = getNameState ( nameMap , tokenId ) ;
5756 return token . Owner ;
5857 }
5958
6059 [ Safe ]
6160 public static Map < string , object > Properties ( ByteString tokenId )
6261 {
6362 StorageMap nameMap = new ( Storage . CurrentContext , Prefix_Name ) ;
64- NameState token = ( NameState ) StdLib . Deserialize ( nameMap [ GetKey ( tokenId ) ] ) ;
65- token . EnsureNotExpired ( ) ;
63+ NameState token = getNameState ( nameMap , tokenId ) ;
6664 Map < string , object > map = new ( ) ;
6765 map [ "name" ] = token . Name ;
6866 map [ "expiration" ] = token . Expiration ;
@@ -193,10 +191,7 @@ public static bool IsAvailable(string name)
193191 if ( rootMap [ fragments [ ^ 1 ] ] is null ) throw new Exception ( "The root does not exist." ) ;
194192 long price = GetPrice ( ( byte ) fragments [ 0 ] . Length ) ;
195193 if ( price < 0 ) return false ;
196- ByteString buffer = nameMap [ GetKey ( name ) ] ;
197- if ( buffer is null ) return true ;
198- NameState token = ( NameState ) StdLib . Deserialize ( buffer ) ;
199- return Runtime . Time >= token . Expiration ;
194+ return parentExpired ( nameMap , 0 , fragments ) ;
200195 }
201196
202197 public static bool Register ( string name , UInt160 owner )
@@ -209,10 +204,9 @@ public static bool Register(string name, UInt160 owner)
209204 string [ ] fragments = SplitAndCheck ( name , false ) ;
210205 if ( fragments is null ) throw new FormatException ( "The format of the name is incorrect." ) ;
211206 if ( rootMap [ fragments [ ^ 1 ] ] is null ) throw new Exception ( "The root does not exist." ) ;
207+ if ( parentExpired ( nameMap , 1 , fragments ) ) throw new InvalidOperationException ( "One of the parent domains has expired." ) ;
212208 ByteString parentKey = GetKey ( fragments [ 1 ] ) ;
213- ByteString parentBuffer = nameMap [ parentKey ] ;
214- if ( parentBuffer is null ) throw new InvalidOperationException ( "Unknown parent domain." ) ;
215- NameState parent = ( NameState ) StdLib . Deserialize ( parentBuffer ) ;
209+ NameState parent = ( NameState ) StdLib . Deserialize ( nameMap [ parentKey ] ) ;
216210 parent . CheckAdmin ( ) ;
217211 if ( ! Runtime . CheckWitness ( owner ) ) throw new InvalidOperationException ( "No authorization." ) ;
218212 long price = GetPrice ( ( byte ) fragments [ 0 ] . Length ) ;
@@ -283,12 +277,11 @@ public static ulong Renew(string name, byte years)
283277 else
284278 Runtime . BurnGas ( price * years ) ;
285279 StorageMap nameMap = new ( Storage . CurrentContext , Prefix_Name ) ;
286- ByteString tokenKey = GetKey ( name ) ;
287- NameState token = ( NameState ) StdLib . Deserialize ( nameMap [ tokenKey ] ) ;
288- token . EnsureNotExpired ( ) ;
280+ NameState token = getNameState ( nameMap , name ) ;
289281 token . Expiration += OneYear * years ;
290282 if ( token . Expiration > Runtime . Time + TenYears )
291283 throw new ArgumentException ( "You can't renew a domain name for more than 10 years in total." ) ;
284+ ByteString tokenKey = GetKey ( name ) ;
292285 nameMap [ tokenKey ] = StdLib . Serialize ( token ) ;
293286 return token . Expiration ;
294287 }
@@ -298,12 +291,11 @@ public static void SetAdmin(string name, UInt160 admin)
298291 if ( name . Length > NameMaxLength ) throw new FormatException ( "The format of the name is incorrect." ) ;
299292 if ( admin is not null && ! Runtime . CheckWitness ( admin ) ) throw new InvalidOperationException ( "No authorization." ) ;
300293 StorageMap nameMap = new ( Storage . CurrentContext , Prefix_Name ) ;
301- ByteString tokenKey = GetKey ( name ) ;
302- NameState token = ( NameState ) StdLib . Deserialize ( nameMap [ tokenKey ] ) ;
303- token . EnsureNotExpired ( ) ;
294+ NameState token = getNameState ( nameMap , name ) ;
304295 if ( ! Runtime . CheckWitness ( token . Owner ) ) throw new InvalidOperationException ( "No authorization." ) ;
305296 UInt160 old = token . Admin ;
306297 token . Admin = admin ;
298+ ByteString tokenKey = GetKey ( name ) ;
307299 nameMap [ tokenKey ] = StdLib . Serialize ( token ) ;
308300 OnSetAdmin ( name , old , admin ) ;
309301 }
@@ -313,8 +305,7 @@ public static void SetRecord(string name, RecordType type, string data)
313305 StorageContext context = Storage . CurrentContext ;
314306 StorageMap nameMap = new ( context , Prefix_Name ) ;
315307 StorageMap recordMap = new ( context , Prefix_Record ) ;
316- string [ ] fragments = SplitAndCheck ( name , true ) ;
317- if ( fragments is null ) throw new FormatException ( "The format of the name is incorrect." ) ;
308+ string tokenId = tokenIDFromName ( name ) ;
318309 switch ( type )
319310 {
320311 case RecordType . A :
@@ -332,11 +323,9 @@ public static void SetRecord(string name, RecordType type, string data)
332323 default :
333324 throw new InvalidOperationException ( "The record type is not supported." ) ;
334325 }
335- string tokenId = name [ ^ ( fragments [ ^ 2 ] . Length + fragments [ ^ 1 ] . Length + 1 ) ..] ;
336- ByteString tokenKey = GetKey ( tokenId ) ;
337- NameState token = ( NameState ) StdLib . Deserialize ( nameMap [ tokenKey ] ) ;
338- token . EnsureNotExpired ( ) ;
326+ NameState token = getNameState ( nameMap , tokenId ) ;
339327 token . CheckAdmin ( ) ;
328+ ByteString tokenKey = GetKey ( tokenId ) ;
340329 byte [ ] recordKey = GetRecordKey ( tokenKey , name , type ) ;
341330 recordMap . PutObject ( recordKey , new RecordState
342331 {
@@ -352,12 +341,9 @@ public static string GetRecord(string name, RecordType type)
352341 StorageContext context = Storage . CurrentContext ;
353342 StorageMap nameMap = new ( context , Prefix_Name ) ;
354343 StorageMap recordMap = new ( context , Prefix_Record ) ;
355- string [ ] fragments = SplitAndCheck ( name , true ) ;
356- if ( fragments is null ) throw new FormatException ( "The format of the name is incorrect." ) ;
357- string tokenId = name [ ^ ( fragments [ ^ 2 ] . Length + fragments [ ^ 1 ] . Length + 1 ) ..] ;
344+ string tokenId = tokenIDFromName ( name ) ;
345+ getNameState ( nameMap , tokenId ) ; // ensure not expired
358346 ByteString tokenKey = GetKey ( tokenId ) ;
359- NameState token = ( NameState ) StdLib . Deserialize ( nameMap [ tokenKey ] ) ;
360- token . EnsureNotExpired ( ) ;
361347 byte [ ] recordKey = GetRecordKey ( tokenKey , name , type ) ;
362348 RecordState record = ( RecordState ) recordMap . GetObject ( recordKey ) ;
363349 if ( record is null ) return null ;
@@ -370,24 +356,22 @@ public static Iterator<RecordState> GetAllRecords(string name)
370356 StorageContext context = Storage . CurrentContext ;
371357 StorageMap nameMap = new ( context , Prefix_Name ) ;
372358 StorageMap recordMap = new ( context , Prefix_Record ) ;
373- ByteString tokenKey = GetKey ( name ) ;
374- NameState token = ( NameState ) StdLib . Deserialize ( nameMap [ tokenKey ] ) ;
375- token . EnsureNotExpired ( ) ;
376- return ( Iterator < RecordState > ) recordMap . Find ( tokenKey , FindOptions . ValuesOnly | FindOptions . DeserializeValues ) ;
359+ string tokenId = tokenIDFromName ( name ) ;
360+ getNameState ( nameMap , tokenId ) ; // ensure not expired
361+ ByteString tokenKey = GetKey ( tokenId ) ;
362+ byte [ ] recordsKey = GetRecordsKey ( tokenKey , name ) ;
363+ return ( Iterator < RecordState > ) recordMap . Find ( recordsKey , FindOptions . ValuesOnly | FindOptions . DeserializeValues ) ;
377364 }
378365
379366 public static void DeleteRecord ( string name , RecordType type )
380367 {
381368 StorageContext context = Storage . CurrentContext ;
382369 StorageMap nameMap = new ( context , Prefix_Name ) ;
383370 StorageMap recordMap = new ( context , Prefix_Record ) ;
384- string [ ] fragments = SplitAndCheck ( name , true ) ;
385- if ( fragments is null ) throw new FormatException ( "The format of the name is incorrect." ) ;
386- string tokenId = name [ ^ ( fragments [ ^ 2 ] . Length + fragments [ ^ 1 ] . Length + 1 ) ..] ;
387- ByteString tokenKey = GetKey ( tokenId ) ;
388- NameState token = ( NameState ) StdLib . Deserialize ( nameMap [ tokenKey ] ) ;
389- token . EnsureNotExpired ( ) ;
371+ string tokenId = tokenIDFromName ( name ) ;
372+ NameState token = getNameState ( nameMap , tokenId ) ;
390373 token . CheckAdmin ( ) ;
374+ ByteString tokenKey = GetKey ( tokenId ) ;
391375 byte [ ] recordKey = GetRecordKey ( tokenKey , name , type ) ;
392376 recordMap . Delete ( recordKey ) ;
393377 }
@@ -422,14 +406,11 @@ private static string Resolve(string name, RecordType type, int redirect)
422406 StorageContext context = Storage . CurrentContext ;
423407 StorageMap nameMap = new ( context , Prefix_Name ) ;
424408 StorageMap recordMap = new ( context , Prefix_Record ) ;
425- string [ ] fragments = SplitAndCheck ( name , true ) ;
426- if ( fragments is null ) throw new FormatException ( "The format of the name is incorrect." ) ;
427- string tokenId = name [ ^ ( fragments [ ^ 2 ] . Length + fragments [ ^ 1 ] . Length + 1 ) ..] ;
409+ string tokenId = tokenIDFromName ( name ) ;
410+ getNameState ( nameMap , tokenId ) ; // ensure not expired
428411 ByteString tokenKey = GetKey ( tokenId ) ;
429- NameState token = ( NameState ) StdLib . Deserialize ( nameMap [ tokenKey ] ) ;
430- token . EnsureNotExpired ( ) ;
431- byte [ ] recordKey = Helper . Concat ( ( byte [ ] ) tokenKey , GetKey ( name ) ) ;
432- return ( Iterator < ( ByteString , RecordState ) > ) recordMap . Find ( recordKey , FindOptions . DeserializeValues ) ;
412+ byte [ ] recordsKey = GetRecordsKey ( tokenKey , name ) ;
413+ return ( Iterator < ( ByteString , RecordState ) > ) recordMap . Find ( recordsKey , FindOptions . DeserializeValues ) ;
433414 }
434415
435416 [ DisplayName ( "_deploy" ) ]
@@ -463,10 +444,15 @@ private static ByteString GetKey(string tokenId)
463444
464445 private static byte [ ] GetRecordKey ( ByteString tokenKey , string name , RecordType type )
465446 {
466- byte [ ] key = Helper . Concat ( ( byte [ ] ) tokenKey , GetKey ( name ) ) ;
447+ byte [ ] key = GetRecordsKey ( tokenKey , name ) ;
467448 return Helper . Concat ( key , ( ( byte ) type ) . ToByteArray ( ) ) ;
468449 }
469450
451+ private static byte [ ] GetRecordsKey ( ByteString tokenKey , string name )
452+ {
453+ return Helper . Concat ( ( byte [ ] ) tokenKey , GetKey ( name ) ) ;
454+ }
455+
470456 private static void PostTransfer ( UInt160 from , UInt160 to , ByteString tokenId , object data )
471457 {
472458 OnTransfer ( from , to , 1 , tokenId ) ;
@@ -619,5 +605,53 @@ private static bool CheckIPv6(string ipv6)
619605 }
620606 return true ;
621607 }
608+
609+ /// <summary>
610+ /// Checks provided name for validness and returns corresponding token ID.
611+ /// </summary>
612+ private static string tokenIDFromName ( string name )
613+ {
614+ string [ ] fragments = SplitAndCheck ( name , true ) ;
615+ if ( fragments is null ) throw new FormatException ( "The format of the name is incorrect." ) ;
616+ if ( fragments . Length == 1 ) return name ;
617+ return name [ ^ ( fragments [ ^ 2 ] . Length + fragments [ ^ 1 ] . Length + 1 ) ..] ;
618+ }
619+
620+ /// <summary>
621+ /// Retrieves NameState from storage and checks that it's not expired as far as the parent domain.
622+ /// </summary>
623+ private static NameState getNameState ( StorageMap nameMap , string tokenId )
624+ {
625+ ByteString tokenBytes = nameMap [ GetKey ( tokenId ) ] ;
626+ if ( tokenBytes is null ) throw new InvalidOperationException ( "Unknown token." ) ;
627+ NameState token = ( NameState ) StdLib . Deserialize ( tokenBytes ) ;
628+ token . EnsureNotExpired ( ) ;
629+ string [ ] fragments = StdLib . StringSplit ( tokenId , "." ) ;
630+ if ( parentExpired ( nameMap , 1 , fragments ) ) throw new InvalidOperationException ( "Parent domain has expired." ) ;
631+ return token ;
632+ }
633+
634+ /// <summary>
635+ /// Returns true if any domain from fragments doesn't exist or expired.
636+ /// </summary>
637+ /// <param name="nameMap">Registered domain names storage map.</param>
638+ /// <param name="first">The deepest subdomain to check.</param>
639+ /// <param name="fragments">The array of domain name fragments.</param>
640+ /// <returns>Whether any domain fragment doesn't exist or expired.</returns>
641+ private static bool parentExpired ( StorageMap nameMap , int first , string [ ] fragments )
642+ {
643+ int last = fragments . Length - 1 ;
644+ string name = fragments [ last ] ;
645+ for ( int i = last ; i >= first ; i -- ) {
646+ if ( i != last ) {
647+ name = fragments [ i ] + "." + name ;
648+ }
649+ ByteString buffer = nameMap [ GetKey ( name ) ] ;
650+ if ( buffer is null ) return true ;
651+ NameState token = ( NameState ) StdLib . Deserialize ( buffer ) ;
652+ if ( Runtime . Time >= token . Expiration ) return true ;
653+ }
654+ return false ;
655+ }
622656 }
623657}
0 commit comments