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);
 | 
						|
}
 |