mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			536 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			536 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| {-
 | |
|   Adapted from original version written by:
 | |
|   /------------------------------------------------------------------------\
 | |
|   | Created for: Telegram (Open Network) Blockchain Contest                |
 | |
|   |      Task 2: DNS Resolver (Automatically registering)                  |
 | |
|   >------------------------------------------------------------------------<
 | |
|   | Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com)    |
 | |
|   |         October 2019                                                   |
 | |
|   \------------------------------------------------------------------------/
 | |
|   Updated to actual DNS standard version by starlightduck in 2022
 | |
| -}
 | |
| 
 | |
| ;;===========================================================================;;
 | |
| ;; Custom ASM instructions                                                   ;;
 | |
| ;;===========================================================================;;
 | |
| 
 | |
| cell udict_get_ref_(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETOPTREF";
 | |
| 
 | |
| ;;===========================================================================;;
 | |
| ;; Utility functions                                                         ;;
 | |
| ;;===========================================================================;;
 | |
| 
 | |
| {-
 | |
|   Data structure:
 | |
|   Root cell: [OptRef<1b+1r?>:Hashmap<PfxDict:Slice->UInt<32b>,CatTable>:domains]
 | |
|          [OptRef<1b+1r?>:Hashmap<UInt<160b>(Time|Hash128)->Slice(DomName)>:gc]
 | |
|          [UInt<32b>:stdperiod] [Gram:PPReg] [Gram:PPCell] [Gram:PPBit]
 | |
|          [UInt<32b>:lasthousekeeping] 
 | |
|   <CatTable> := HashmapE 256 (~~16~~) ^DNSRecord
 | |
|   
 | |
|   STORED DOMAIN NAME SLICE FORMAT: (#ZeroChars<7b>) (Domain name value)
 | |
|   #Zeros allows to simultaneously store, for example, com\0 and com\0google\0
 | |
|   That will be stored as \1com\0 and \2com\0google\0 (pfx tree has restricitons)
 | |
|   This will allow to resolve more specific requests to subdomains, and resort
 | |
|       to parent domain next resolver lookup if subdomain is not found
 | |
|   com\0goo\0 lookup will, for example look up \2com\0goo\0 and then
 | |
|       \1com\0goo\0 which will return \1com\0 (as per pfx tree) with -1 cat
 | |
| -}
 | |
| 
 | |
| (cell, cell, cell, [int, int, int, int], int, int) load_data() inline_ref {
 | |
|   slice cs = get_data().begin_parse();
 | |
|   return (
 | |
|     cs~load_ref(),        ;; control data
 | |
|     cs~load_dict(),       ;; pfx tree: domains data and exp
 | |
|     cs~load_dict(),       ;; gc auxillary with expiration and 128-bit hash slice
 | |
|     [ cs~load_uint(30),   ;; length of this period of time in seconds
 | |
|       cs~load_grams(),    ;; standard payment for registering a new subdomain
 | |
|       cs~load_grams(),    ;; price paid for each cell (PPC)
 | |
|       cs~load_grams() ],  ;;                 and bit  (PPB)
 | |
|     cs~load_uint(32),     ;; next housekeeping to be done at
 | |
|     cs~load_uint(32)      ;; last housekeeping done at
 | |
|   );
 | |
| }
 | |
| 
 | |
| (int, int, int, int) load_prices() inline_ref {
 | |
|   slice cs = get_data().begin_parse();
 | |
|   (cs~load_ref(), cs~load_dict(), cs~load_dict());
 | |
|   return (cs~load_uint(30), cs~load_grams(), cs~load_grams(), cs~load_grams());
 | |
| }
 | |
| 
 | |
| () store_data(cell ctl, cell dd, cell gc, prices, int nhk, int lhk) impure {
 | |
|   var [sp, ppr, ppc, ppb] = prices;
 | |
|   set_data(begin_cell()
 | |
|       .store_ref(ctl) ;; control data
 | |
|       .store_dict(dd) ;; domains data and exp
 | |
|       .store_dict(gc) ;; keyed expiration time and 128-bit hash slice
 | |
|       .store_uint(sp, 30) ;; standard period
 | |
|       .store_grams(ppr) ;; price per registration
 | |
|       .store_grams(ppc) ;; price per cell
 | |
|       .store_grams(ppb) ;; price per bit
 | |
|       .store_uint(nhk, 32) ;; next housekeeping
 | |
|       .store_uint(lhk, 32) ;; last housekeeping
 | |
|       .end_cell());
 | |
| }
 | |
| 
 | |
| global var query_info;
 | |
| 
 | |
| () send_message(slice addr, int tag,   int query_id, 
 | |
|                 int   body, int grams, int mode) impure {
 | |
|   ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 
 | |
|   ;;                src:MsgAddress -> 011000 0x18
 | |
|   var msg = begin_cell()
 | |
|     .store_uint (0x18, 6)
 | |
|     .store_slice(addr)
 | |
|     .store_grams(grams)
 | |
|     .store_uint (0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
 | |
|     .store_uint (tag, 32)
 | |
|     .store_uint (query_id, 64);
 | |
|   if (body >= 0) {
 | |
|     msg~store_uint(body, 32);
 | |
|   }
 | |
|   send_raw_message(msg.end_cell(), mode);
 | |
| }
 | |
| 
 | |
| () send_error(int error_code) impure {
 | |
|   var (addr, query_id, op) = query_info;
 | |
|   return send_message(addr, error_code, query_id, op, 0, 64);
 | |
| }
 | |
| 
 | |
| () send_ok(int price) impure {
 | |
|   raw_reserve(price, 4);
 | |
|   var (addr, query_id, op) = query_info;
 | |
|   return send_message(addr, 0xef6b6179, query_id, op, 0, 128);
 | |
| }
 | |
| 
 | |
| () housekeeping(cell ctl, cell dd, cell gc, prices, int nhk, int lhk, int max_steps) impure {
 | |
|   int n = now();
 | |
|   if (n < max(nhk, lhk + 60)) { ;; housekeeping cooldown: 1 minute
 | |
|     ;; if housekeeping was done recently, or if next housekeeping is in the future, just save
 | |
|     return store_data(ctl, dd, gc, prices, nhk, lhk);
 | |
|   }
 | |
|   ;; need to do some housekeeping - maybe remove entry with
 | |
|   ;;     least expiration but only if it is already expired
 | |
|   ;; no iterating and deleting all to not put too much gas gc
 | |
|   ;;     burden on any random specific user request
 | |
|   ;; over time it will do the garbage collection required
 | |
|   (int mkey, _, int found?) = gc.udict_get_min?(256);
 | |
|   while (found? & max_steps) { ;; no short circuit optimization, two nested ifs
 | |
|     nhk = (mkey >> (256 - 32));
 | |
|     if (nhk < n) {
 | |
|       int key = mkey % (1 << (256 - 32));
 | |
|       (slice val, found?) = dd.udict_get?(256 - 32, key);
 | |
|       if (found?) {
 | |
|         int exp = val.preload_uint(32);
 | |
|         if (exp <= n) {
 | |
|           dd~udict_delete?(256 - 32, key);
 | |
|         }
 | |
|       }
 | |
|       gc~udict_delete?(256, mkey);
 | |
|       (mkey, _, found?) = gc.udict_get_min?(256);
 | |
|       nhk = (found? ? mkey >> (256 - 32) : 0xffffffff);
 | |
|       max_steps -= 1;
 | |
|     } else {
 | |
|       found? = false;
 | |
|     }
 | |
|   }
 | |
|   store_data(ctl, dd, gc, prices, nhk, n);
 | |
| }
 | |
| 
 | |
| int calcprice_internal(slice domain, cell data, ppc, ppb) inline_ref { ;; only for internal calcs
 | |
|   var (_, bits, refs) = compute_data_size(data, 100);  ;; 100 cells max
 | |
|   bits += slice_bits(domain) * 2 + (128 + 32 + 32);
 | |
|   return ppc * (refs + 2) + ppb * bits;
 | |
| }
 | |
| 
 | |
| int check_owner(cell cat_table, cell owner_info, int src_wc, int src_addr, int strict) inline_ref {
 | |
|   if (strict & cat_table.null?()) { ;; domain not found: return notf | 2^31
 | |
|     return 0xee6f7466;
 | |
|   }
 | |
|   if (owner_info.null?()) { ;; no owner on this domain: no-2 (in strict mode), ok else
 | |
|     return strict & 0xee6f2d32;
 | |
|   }
 | |
|   var ERR_BAD2 = 0xe2616432;
 | |
|   slice sown = owner_info.begin_parse();
 | |
|   if (sown.slice_bits() < 16 + 3 + 8 + 256) { ;; bad owner record: bad2
 | |
|     return ERR_BAD2;
 | |
|   }
 | |
|   if (sown~load_uint(16 + 3) != 0x9fd3 * 8 + 4) {
 | |
|     return ERR_BAD2;
 | |
|   }
 | |
|   (int owner_wc, int owner_addr) = (sown~load_int(8), sown.preload_uint(256));
 | |
|   if ((owner_wc != src_wc) | (owner_addr != src_addr)) { ;; not owner: nown
 | |
|     return 0xee6f776e;
 | |
|   }
 | |
|   return 0;  ;; ok
 | |
| }
 | |
| 
 | |
| ;;===========================================================================;;
 | |
| ;; Internal message handler (Code 0)                                         ;;
 | |
| ;;===========================================================================;;
 | |
| 
 | |
| {-
 | |
|   Internal message cell structure:
 | |
|                    8             4           2            1
 | |
|     int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
 | |
|     src:MsgAddressInt dest:MsgAddressInt
 | |
|     value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
 | |
|     created_lt:uint64 created_at:uint32  
 | |
|   Internal message data structure:
 | |
|     [UInt<32b>:op] [UInt<64b>:query_id] [Ref<1r>:domain] 
 | |
|     (if not prolong: [Ref<1r>:value->CatTable])
 | |
|     
 | |
| -}
 | |
| 
 | |
| ;; Control operations: permitted only to the owner of this smartcontract
 | |
| () perform_ctl_op(int op, int src_wc, int src_addr, slice in_msg) impure inline_ref {
 | |
|   var (ctl, domdata, gc, prices, nhk, lhk) = load_data();
 | |
|   var cs = ctl.begin_parse();
 | |
|   if ((cs~load_int(8) != src_wc) | (cs~load_uint(256) != src_addr)) {
 | |
|     return send_error(0xee6f776e);
 | |
|   }
 | |
|   if (op == 0x43685072) {  ;; ChPr = Change Prices
 | |
|     var (stdper, ppr, ppc, ppb) = (in_msg~load_uint(32), in_msg~load_grams(), in_msg~load_grams(), in_msg~load_grams());
 | |
|     in_msg.end_parse();
 | |
|     ;; NB: stdper == 0 -> disable new actions
 | |
|     store_data(ctl, domdata, gc, [stdper, ppr, ppc, ppb], nhk, lhk);
 | |
|     return send_ok(0);
 | |
|   }
 | |
|   var (addr, query_id, op) = query_info;
 | |
|   if (op == 0x4344656c) {  ;; CDel = destroy smart contract
 | |
|     ifnot (domdata.null?()) {
 | |
|       ;; domain dictionary not empty, force gc
 | |
|       housekeeping(ctl, domdata, gc, prices, nhk, 1, -1);
 | |
|     }
 | |
|     (ctl, domdata, gc, prices, nhk, lhk) = load_data();
 | |
|     ifnot (domdata.null?()) {
 | |
|       ;; domain dictionary still not empty, error
 | |
|       return send_error(0xee74656d);
 | |
|     }
 | |
|     return send_message(addr, 0xef6b6179, query_id, op, 0, 128 + 32);
 | |
|   }
 | |
|   if (op == 0x54616b65) { ;; Take = take grams from the contract
 | |
|     var amount = in_msg~load_grams();
 | |
|     return send_message(addr, 0xef6b6179, query_id, op, amount, 64);
 | |
|   }
 | |
|   return send_error(0xffffffff);
 | |
| }
 | |
| 
 | |
| ;; Must send at least GR$1 more for possible gas fees!
 | |
| () recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
 | |
|   ;; this time very interested in internal messages
 | |
|   if (in_msg.slice_bits() < 32) { 
 | |
|     return ();  ;; simple transfer or short
 | |
|   }
 | |
|   slice cs = in_msg_cell.begin_parse();
 | |
|   int flags = cs~load_uint(4);
 | |
|   if (flags & 1) { 
 | |
|     return ();  ;; bounced messages
 | |
|   }
 | |
|   slice s_addr = cs~load_msg_addr();
 | |
|   (int src_wc, int src_addr) = s_addr.parse_std_addr();
 | |
|   int op = in_msg~load_uint(32);
 | |
|   ifnot (op) { 
 | |
|     return ();   ;; simple transfer with comment
 | |
|   }
 | |
|   int query_id = 0;
 | |
|   if (in_msg.slice_bits() >= 64) { 
 | |
|     query_id = in_msg~load_uint(64);
 | |
|   }
 | |
|   
 | |
|   query_info = (s_addr, query_id, op);
 | |
|   
 | |
|   if (op & (1 << 31)) {
 | |
|     return ();   ;; an answer to our query
 | |
|   }
 | |
|   if ((op >> 24) == 0x43) {
 | |
|     ;; Control operations
 | |
|     return perform_ctl_op(op, src_wc, src_addr, in_msg);
 | |
|   }
 | |
|   
 | |
|   int qt = (op == 0x72656764) * 1 + (op == 0x70726f6c) * 2 + (op == 0x75706464) * 4 + (op == 0x676f6763) * 8;
 | |
|   ifnot (qt) { ;; unknown query, return error
 | |
|     return send_error(0xffffffff);
 | |
|   }
 | |
|   qt = - qt;
 | |
|   
 | |
|   (cell ctl, cell domdata, cell gc, [int, int, int, int] prices, int nhk, int lhk) = load_data();
 | |
|   
 | |
|   if (qt == 8) { ;; 0x676f6763 -> GO, GC! go!!!
 | |
|     ;; Manual garbage collection iteration
 | |
|     int max_steps = in_msg~load_int(32);   ;; -1 = infty
 | |
|     housekeeping(ctl, domdata, gc, prices, nhk, 1, max_steps); ;; forced
 | |
|     return send_error(0xef6b6179);
 | |
|   }
 | |
| 
 | |
|   slice domain = null();
 | |
|   cell domain_cell = in_msg~load_maybe_ref();
 | |
|   int fail = 0;
 | |
|   if (domain_cell.null?()) {
 | |
|     int bytes = in_msg~load_uint(6);
 | |
|     fail = (bytes == 0);
 | |
|     domain = in_msg~load_bits(bytes * 8);
 | |
|   } else {
 | |
|     domain = domain_cell.begin_parse();
 | |
|     var (bits, refs) = slice_bits_refs(domain);
 | |
|     fail = (refs | ((bits - 8) & (7 - 128)));
 | |
|   }
 | |
| 
 | |
|   ifnot (fail) {
 | |
|     ;; domain must end with \0! no\0 error
 | |
|     fail = domain.slice_last(8).preload_uint(8);
 | |
|   }
 | |
|   if (fail) {
 | |
|     return send_error(0xee6f5c30);
 | |
|   }
 | |
|     
 | |
|   int n = now();
 | |
|   cell cat_table = cell owner_info = null();
 | |
|   int key = int exp = int zeros = 0;
 | |
|   slice tail = domain;
 | |
|   repeat (tail.slice_bits() ^>> 3) {
 | |
|     cat_table = null();
 | |
|     int z = (tail~load_uint(8) == 0);
 | |
|     zeros -= z;
 | |
|     if (z) {
 | |
|       key = (string_hash(domain.skip_last_bits(tail.slice_bits())) >> 32);
 | |
|       var (val, found?) = domdata.udict_get?(256 - 32, key);
 | |
|       if (found?) {
 | |
|         exp = val~load_uint(32);
 | |
|         if (exp >= n) {  ;; entry not expired
 | |
|           cell cat_table = val~load_ref();
 | |
|           val.end_parse();
 | |
|           ;; update: category length now u256 instead of i16, owner index is now 0 instead of -2
 | |
|           var (cown, ok) = cat_table.udict_get_ref?(256, 0);
 | |
|           if (ok) {
 | |
|             owner_info = cown;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   if (zeros > 4) { ;; too much zero chars (overflow): ov\0
 | |
|     return send_error(0xef765c30);
 | |
|   }
 | |
| 
 | |
|   ;; ##########################################################################
 | |
|   
 | |
|   int err = check_owner(cat_table, owner_info, src_wc, src_addr, qt != 1);
 | |
|   if (err) {
 | |
|     return send_error(err);
 | |
|   }
 | |
| 
 | |
|   ;; ##########################################################################
 | |
|   
 | |
|   ;; load desired data (reuse old for a "prolong" operation)
 | |
|   cell data = null();
 | |
|   
 | |
|   if (qt != 2) {  ;; not a "prolong", load data dictionary
 | |
|     data = in_msg~load_ref();
 | |
|     ;; basic integrity check of (client-provided) dictionary
 | |
|     ifnot (data.dict_empty?()) { ;; 1000 gas!
 | |
|       ;; update: category length now u256 instead of i16, owner index is now 0 instead of -2
 | |
|       var (oinfo, ok) = data.udict_get_ref?(256, 0);
 | |
|       if (ok) {
 | |
|         var cs = oinfo.begin_parse();
 | |
|         throw_unless(31, cs.slice_bits() >= 16 + 3 + 8 + 256);
 | |
|         throw_unless(31, cs.preload_uint(19) == 0x9fd3 * 8 + 4);
 | |
|       }
 | |
|       (_, _, int minok) = data.udict_get_min?(256); ;; update: category length now u256 instead of i16
 | |
|       (_, _, int maxok) = data.udict_get_max?(256); ;; update: category length now u256 instead of i16
 | |
|       throw_unless(31, minok & maxok);
 | |
|     }
 | |
|   } else {
 | |
|     data = cat_table;
 | |
|   }
 | |
| 
 | |
|   ;; load prices
 | |
|   var [stdper, ppr, ppc, ppb] = prices;
 | |
|   ifnot (stdper) {  ;; smart contract disabled by owner, no new actions
 | |
|     return send_error(0xd34f4646);
 | |
|   }
 | |
| 
 | |
|   ;; compute action price
 | |
|   int price = calcprice_internal(domain, data, ppc, ppb) + (ppr & (qt != 4));
 | |
|   if (msg_value - (1 << 30) < price) { ;; gr<p: grams - GR$1 < price
 | |
|     return send_error(0xe7723c70);
 | |
|   }
 | |
|   
 | |
|   ;; load desired expiration unixtime
 | |
|   int req_expires_at = in_msg~load_uint(32);
 | |
| 
 | |
|   ;; ##########################################################################
 | |
|   if (qt == 2) { ;; 0x70726f6c -> prol | prolong domain
 | |
|     if (exp > n + stdper) {  ;; does not expire soon, cannot prolong
 | |
|       return send_error(0xf365726f);
 | |
|     }
 | |
|     domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(exp + stdper, 32).store_ref(data));
 | |
| 
 | |
|     int gckeyO = (exp << (256 - 32)) + key;
 | |
|     int gckeyN = gckeyO + (stdper << (256 - 32));
 | |
|     gc~udict_delete?(256, gckeyO); ;; delete old gc entry, add new
 | |
|     gc~udict_set_builder(256, gckeyN, begin_cell());
 | |
|     
 | |
|     housekeeping(ctl, domdata, gc, prices, nhk, lhk, 1);
 | |
|     return send_ok(price);
 | |
|   }
 | |
|   
 | |
|   ;; ##########################################################################
 | |
|   if (qt == 1) { ;; 0x72656764 -> regd | register domain
 | |
|     ifnot (cat_table.null?()) { ;; domain already exists: return alre | 2^31
 | |
|       return send_error(0xe16c7265);
 | |
|     }
 | |
|     int expires_at = n + stdper;
 | |
|     domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(expires_at, 32).store_ref(data));
 | |
| 
 | |
|     int gckey = (expires_at << (256 - 32)) | key;
 | |
|     gc~udict_set_builder(256, gckey, begin_cell());
 | |
| 
 | |
|     housekeeping(ctl, domdata, gc, prices, min(nhk, expires_at), lhk, 1);
 | |
|     return send_ok(price);
 | |
|   }
 | |
| 
 | |
|   ;; ##########################################################################
 | |
|   if (qt == 4) { ;; 0x75706464 -> updd | update domain (data)
 | |
|     domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(exp, 32).store_ref(data));
 | |
|     housekeeping(ctl, domdata, gc, prices, nhk, lhk, 1);
 | |
|     return send_ok(price);
 | |
|   }
 | |
|   ;; ##########################################################################
 | |
|   
 | |
|   return (); ;; should NEVER reach this part of code!
 | |
| }
 | |
| 
 | |
| ;;===========================================================================;;
 | |
| ;; External message handler (Code -1)                                        ;;
 | |
| ;;===========================================================================;;
 | |
| 
 | |
| () recv_external(slice in_msg) impure {
 | |
|   ;; only for initialization
 | |
|   (cell ctl, cell dd, cell gc, var prices, int nhk, int lhk) = load_data();
 | |
|   ifnot (lhk) {
 | |
|     accept_message();
 | |
|     return store_data(ctl, dd, gc, prices, 0xffffffff, now());
 | |
|   }
 | |
| }
 | |
| 
 | |
| ;;===========================================================================;;
 | |
| ;; Getter methods                                                            ;;
 | |
| ;;===========================================================================;;
 | |
| 
 | |
| (int, cell, int, slice) dnsdictlookup(slice domain, int nowtime) inline_ref {
 | |
|   (int bits, int refs) = domain.slice_bits_refs();
 | |
|   throw_if(30, refs | (bits & 7)); ;; malformed input (~ 8n-bit)
 | |
|   ifnot (bits) {
 | |
|     ;; return (0, null(), 0, null());  ;; zero-length input
 | |
|     throw(30); ;; update: throw exception for empty input
 | |
|   }
 | |
| 
 | |
|   int domain_last_byte = domain.slice_last(8).preload_uint(8);
 | |
|   if (domain_last_byte) {
 | |
|     domain = begin_cell().store_slice(domain) ;; append zero byte
 | |
|                          .store_uint(0, 8).end_cell().begin_parse();
 | |
|     bits += 8;
 | |
|   }
 | |
|   if (bits == 8) {
 | |
|     return (0, null(), 8, null()); ;; zero-length input, but with zero byte
 | |
|     ;; update: return 8 as resolved, but with no data
 | |
|   }
 | |
|   int domain_first_byte = domain.preload_uint(8);
 | |
|   if (domain_first_byte == 0) {
 | |
|     ;; update: remove prefix \0
 | |
|     domain~load_uint(8);
 | |
|     bits -= 8;
 | |
|   }
 | |
|   var ds = get_data().begin_parse();
 | |
|   (_, cell root) = (ds~load_ref(), ds~load_dict());
 | |
|   
 | |
|   slice val = null();
 | |
|   int tail_bits = -1;
 | |
|   slice tail = domain;
 | |
| 
 | |
|   repeat (bits >> 3) {
 | |
|     if (tail~load_uint(8) == 0) {
 | |
|       var key = (string_hash(domain.skip_last_bits(tail.slice_bits())) >> 32);
 | |
|       var (v, found?) = root.udict_get?(256 - 32, key);
 | |
|       if (found?) {
 | |
|         if (v.preload_uint(32) >= nowtime) {  ;; entry not expired
 | |
|           val = v;
 | |
|           tail_bits = tail.slice_bits();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (val.null?()) {
 | |
|     return (0, null(), 0, null()); ;; failed to find entry in subdomain dictionary
 | |
|   }
 | |
| 
 | |
|   return (val~load_uint(32), val~load_ref(), tail_bits == 0, domain.skip_last_bits(tail_bits));
 | |
| }
 | |
| 
 | |
| ;;8m  dns-record-value
 | |
| (int, cell) dnsresolve(slice domain, int category) method_id {
 | |
|   (int exp, cell cat_table, int exact?, slice pfx) = dnsdictlookup(domain, now());
 | |
|   ifnot (exp) {
 | |
|     return (exact?, null()); ;; update: reuse exact? to return 8 for \0
 | |
|   }
 | |
|   ifnot (exact?) { ;; incomplete subdomain found, must return next resolver (-1)
 | |
|     category = "dns_next_resolver"H; ;; 0x19f02441ee588fdb26ee24b2568dd035c3c9206e11ab979be62e55558a1d17ff
 | |
|     ;; update: next resolver is now sha256("dns_next_resolver") instead of -1
 | |
|   }
 | |
|   
 | |
|   int pfx_bits = pfx.slice_bits();
 | |
|   
 | |
|   ;; pfx.slice_bits() will contain 8m, where m is number of bytes in subdomain 
 | |
|   ;;   COUNTING the zero byte (if structurally correct: no multiple-ZB keys)
 | |
|   ;;   which corresponds to 8m, m=one plus the number of bytes in the subdomain found)
 | |
|   ifnot (category) {
 | |
|     return (pfx_bits, cat_table); ;; return cell with entire dictionary for 0
 | |
|   } else {
 | |
|     cell cat_found = cat_table.udict_get_ref_(256, category); ;; update: category length now u256 instead of i16
 | |
|     return (pfx_bits, cat_found);
 | |
|   }
 | |
| }
 | |
| 
 | |
| ;; getexpiration needs to know the current time to skip any possible expired
 | |
| ;;     subdomains in the chain. it will return 0 if not found or expired.
 | |
| int getexpirationx(slice domain, int nowtime) inline method_id {
 | |
|   (int exp, _, _, _) = dnsdictlookup(domain, nowtime);
 | |
|   return exp;
 | |
| }
 | |
| 
 | |
| int getexpiration(slice domain) method_id {
 | |
|   return getexpirationx(domain, now());
 | |
| }
 | |
| 
 | |
| int getstdperiod() method_id {
 | |
|   (int stdper, _, _, _) = load_prices();
 | |
|   return stdper;
 | |
| }
 | |
| 
 | |
| int getppr() method_id {
 | |
|   (_, int ppr, _, _) = load_prices();
 | |
|   return ppr;
 | |
| }
 | |
| 
 | |
| int getppc() method_id {
 | |
|   (_, _, int ppc, _) = load_prices();
 | |
|   return ppc;
 | |
| }
 | |
| 
 | |
| int getppb() method_id {
 | |
|   ( _, _, _, int ppb) = load_prices();
 | |
|   return ppb;
 | |
| }
 | |
| 
 | |
| int calcprice(slice domain, cell val) method_id { ;; only for external gets (not efficient)
 | |
|   (_, _, int ppc, int ppb) = load_prices();
 | |
|   return calcprice_internal(domain, val, ppc, ppb);
 | |
| }
 | |
| 
 | |
| int calcregprice(slice domain, cell val) method_id { ;; only for external gets (not efficient)
 | |
|   (_, int ppr, int ppc, int ppb) = load_prices();
 | |
|   return ppr + calcprice_internal(domain, val, ppc, ppb);
 | |
| }
 |