;;; ---------------------------------------------------------------------------
;;; @title A public resolver; only allows the node's owner to set its address.
;;; @author Daniel Ellison <daniel@syrinx.net>

(seq

  ;; --------------------------------------------------------------------------
  ;; Constant definitions.

  ;; Booleans.
  (def 'true 1)
  (def 'false 0)

  ;; Miscellaneous constants.
  (def 'content-hash     0x01) ; Offset into node record for content hash.
  (def 'invalid-location 0x02) ; Jumping here causes an EVM error.

  ;; Memory layout.
  (def 'scratch     0x00) ; Scratch space.
  (def 'call-data   0x20) ; Data provided to called contract.
  (def 'return-data 0x40) ; Data returned from called contract.

  ;; Storage layout.
  (def 'ens-registry 0x00) ; Address of ens registry.

  ;; Precomputed function IDs.
  (def 'supports-interface 0x01ffc9a7) ; supportsInterface(bytes4)
  (def 'get-node-address   0x3b3b57de) ; addr(bytes32)
  (def 'set-node-address   0xd5fa2b00) ; setAddr(bytes32,address)
  (def 'get-content-hash   0x2dff6941) ; content(bytes32)
  (def 'set-content-hash   0xc3d014d6) ; setContent(bytes32,bytes32)
  (def 'get-node-owner     0x02571be3) ; owner(bytes32)

  ;; --------------------------------------------------------------------------
  ;; @notice Shifts the rightmost 4 bytes of a 32-byte number left by 28 bytes.
  ;; @param input A 32-byte number.

  (def 'shift-left (input)
    (mul input (exp 2 224)))

  ;; --------------------------------------------------------------------------
  ;; @notice Shifts the leftmost 4 bytes of a 32-byte number right by 28 bytes.
  ;; @param input A 32-byte number.

  (def 'shift-right (input)
    (div input (exp 2 224)))

  ;; --------------------------------------------------------------------------
  ;; @notice Determines whether the supplied function ID matches a known
  ;;         function hash and executes <code-body> if so.
  ;; @dev The function ID is in the leftmost four bytes of the call data.
  ;; @param function-hash The four-byte hash of a known function signature.
  ;; @param code-body The code to run in the case of a match.

  (def 'function (function-hash code-body)
    (when (= (shift-right (calldataload 0x00)) function-hash)
      code-body))

  ;; --------------------------------------------------------------------------
  ;; @notice Hashes a given node to determine its actual storage location.
  ;; @param node Hash this node address.

  (def 'hash-location (node)
    (seq
      (mstore scratch node)
      (sha3 scratch 32)))

  ;; --------------------------------------------------------------------------
  ;; @notice Looks up the address for the node passed in.
  ;; @param node Get the address for this node.

  (def 'get-address (node)
    (sload (hash-location node)))

  ;; --------------------------------------------------------------------------
  ;; @notice Stores the address for the node passed in.
  ;; @param node Set the address for this node.
  ;; @param addr The address to set.

  (def 'set-address (node addr)
    (sstore (hash-location node) addr))

  ;; --------------------------------------------------------------------------
  ;; @notice Looks up the content hash for the node passed in.
  ;; @param node Get the content hash for this node.

  (def 'get-content (node)
    (sload (+ (hash-location node) content-hash)))

  ;; --------------------------------------------------------------------------
  ;; @notice Stores the content hash for the node passed in.
  ;; @param node Set the content hash for this node.
  ;; @param hash The content hash to set.

  (def 'set-content (node hash)
    (sstore (+ (hash-location node) content-hash) hash))

  ;; --------------------------------------------------------------------------
  ;; @notice Checks that the caller is the node owner.
  ;; @param node Check the owner of this node.

  (def 'only-owner (node)
    (seq

      ;; Set up the call data.
      (mstore (+ call-data 0x00) (shift-left get-node-owner))
      (mstore (+ call-data 0x04) node)

      ;; Call ens.owner(node). Bail on fail.
      (when (= false (call (gas) 0 @@ens-registry call-data 36 return-data 32))
        (jump invalid-location))

      ;; If the caller isn't the current node owner, throw an exception.
      (when (!= @return-data (caller))
        (jump invalid-location))))

  ;; --------------------------------------------------------------------------
  ;; INIT

  ;; Store the constructor parameter.
  (codecopy scratch (bytecodesize) 32)
  (sstore ens-registry @scratch)

  ;; --------------------------------------------------------------------------
  ;; CODE

  (returnlll
    (seq

      ;; ----------------------------------------------------------------------
      ;; @notice Returns true if the resolver implements the interface
      ;;         specified by the provided hash.
      ;; @param interface-id The ID of the interface to check for.
      ;; @return True if the contract implements the requested interface.

      (def 'interface-id (calldataload 0x04))

      (function supports-interface
        (seq

          ;; Do the required comparisons and store for return.
          (mstore scratch (|| (= interface-id 0x3b3b57de)
                              (= interface-id 0xd8389dc5)))

          ;; Return boolean result.
          (return scratch 32)))

      ;; ----------------------------------------------------------------------
      ;; @notice Returns the address associated with an ENS node.
      ;; @param node The ENS node to query.
      ;; @return The associated address.

      (def 'node (calldataload 0x04))

      (function get-node-address
        (seq

          ;; Retrieve the node's address.
          (mstore scratch (get-address node))

          ;; Return result.
          (return scratch 32)))

      ;; ----------------------------------------------------------------------
      ;; @notice Sets the address associated with an ENS node. May only be
      ;;         called by the owner of that node in the ENS registry.
      ;; @param node The node to update.
      ;; @param addr The address to set.

      (def 'node (calldataload 0x04))
      (def 'addr (calldataload 0x24))

      (function set-node-address
        (seq (only-owner node)

          ;; Store the node's new address.
          (set-address node addr)

          (stop)))

      ;; ----------------------------------------------------------------------
      ;; @notice Returns the content hash associated with an ENS node. Note
      ;;         that this resource type is not standardized, and will likely
      ;;         change in future to a resource type based on multihash.
      ;; @param node The ENS node to query.
      ;; @return The associated content hash.

      (def 'node (calldataload 0x04))

      (function get-content-hash
        (seq

          ;; Retrieve the node's content hash.
          (mstore scratch (get-content node))

          ;; Return result.
          (return scratch 32)))

      ;; ----------------------------------------------------------------------
      ;; @notice Sets the hash associated with an ENS node. May only be
      ;;         called by the owner of that node in the ENS registry. Note
      ;;         that this resource type is not standardized, and will likely
      ;;         change in future to a resource type based on multihash.
      ;; @param node The node to update.
      ;; @param hash The content hash to set.

      (def 'node (calldataload 0x04))
      (def 'hash (calldataload 0x24))

      (function set-content-hash
        (seq (only-owner node)

          ;; Store the node's new content hash.
          (set-content node hash)

          (stop)))

      ;; ----------------------------------------------------------------------
      ;; @notice Fallback: No functions matched the function ID provided.

      (jump invalid-location)))

)
