Search code examples
smartcontractspactkadena

coin-v5.pact:516:12: Single-key account protocol violation


Hi guys I really need help on this, I have a domain name registry smart contract with file name "name.pact" and a REPL file named "name.repl". This is the file structure:

--->smartcontract 
    -->root 
        -->coin-v5.pact 
        -->fungible-v2.pact 
        -->fungible-xchain-v1.pact 
    -->name.pact 
    -->name.repl

This is my name.pact file

(namespace 'free)
(define-keyset "free.domain-admin-gov-keyset" (read-keyset 'domain-admin-gov-keyset))

(module testingdomain GOVERNANCE

  ; --------------------------------------------------------------------------
  ; Schemas and Tables

  ; subdomain-schema
  (defschema subdomain
    name:string
    address:string
  )

  ;define names schema
  (defschema names
    owner:string
    lastPrice:decimal
    subdomains:list
  )

  ;define name map schema
  (defschema name-map
    address:string
    top-level-name:string
  )

  ;define address map schema
  (defschema address-map
    name:string
    top-level-name:string
  )

  ;define sales schema
  (defschema sales
    price:decimal
    sellable:bool
  )

  (deftable names-table:{names})
  (deftable name-map-table:{name-map})
  (deftable address-map-table:{address-map})
  (deftable sales-table:{sales})

  ; --------------------------------------------------------------------------
  ; Constants

  (defconst BASE_ONEYEAR_PRICE 1.0)
  (defconst BASE_TWOYEAR_PRICE (* (* BASE_ONEYEAR_PRICE 2) 0.95))
  (defconst SUBDOMAIN_PRICE 3.0)
  (defconst UPDATE_PRICE 0.5)
  (defconst SELL_FEE_PERCENTAGE 5.0)
  (defconst EXPIRATION_GRACE_PERIOD 31)
  (defconst VAULT_ACCOUNT "k:36990b871267ec4532551e505260806d7f39378cebb5ea2c998c80301c5a100f")

  (defconst ALLOWED_CHARS:list ["-", "_", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"])

  ; --------------------------------------------------------------------------
  ; Utils

  (defun curr-time:time ()
    @doc "Returns current chain's block-time in time type"
    (at 'block-time (chain-data)))


  ; --------------------------------------------------------------------------
  ; Capabilities

  (use coin [ details transfer ])

  (defcap GOVERNANCE ()
    "Module governance capability that only allows the admin to update this module"
    ;; Check if the tx was signed with the provided keyset, fail if not
    (enforce-keyset "free.doamin-admin-gov-keyset"))

  (defcap ACCOUNT_GUARD (account)
    (enforce-guard (at 'guard (coin.details account)))
  )

  (defcap NAME_AVAILABLE (name:string)
    @doc "Validate if the name is available "

    (with-default-read names-table name
      { "expiryDate": (add-time (curr-time) (days (* -1 EXPIRATION_GRACE_PERIOD))) }
      { "expiryDate":= expiryDate }

      (enforce (>= (curr-time) (add-time expiryDate (days EXPIRATION_GRACE_PERIOD))) "Name not available")
    )
  )

  (defcap NAME_UPSERT (name:string)
    "New name has been added or updated"
    @event
    true
  )

  (defcap ITEM_SOLD (name:string prevOwner:string newOwner:string price:decimal)
    "An item has been sold"
    @event
    true
  )

  (defcap SALE_STATUS_UPDATED (name:string)
    "An item has been put up for and removed from sale"
    @event
    true
  )

  (defcap MAPPING () true)

  ; --------------------------------------------------------------------------
  ; Functions

  (defun enforce-domain (name:string)
    (enforce (= ".kda" (take -4 name)) "Domain should be .kda")
  )

  (defun strip-domain (name:string)
    (drop -4 name)
  )

  (defun enforce-name-is-valid (name:string)
    (enforce (<= (length name) 35) "Maximum 35 characters allowed in name")
    (enforce (!= name "") "Name should not be empty")
    (let*
      (
        (validate-character (lambda (character)
          (enforce (= true (contains character ALLOWED_CHARS)) "No forbidden chars allowed")
        ))
      )
      (map (validate-character) (str-to-list name))
    )
  )

  (defun enforce-address-is-valid (address: string)
    (enforce-one "Only k: and w: accounts are supported" [
      (enforce (= (take 2 address) "k:") "k:account")
      (enforce (= (take 2 address) "w:") "w:account")
    ])
  )

  (defun enforce-days-is-valid (nrDays:integer)
   (enforce-one "Invalid days" [
     (enforce (= nrDays 365) "One year")
     (enforce (= nrDays 730) "Two years")
   ])
  )

  (defun enforce-address-not-in-use (address: string)
    (with-default-read address-map-table address
      {
        "name" : "",
        "top-level-name" : ""
      }
      {
        "name" := name,
        "top-level-name" := toplevelname
      }

      (with-default-read names-table toplevelname
        { "expiryDate": (add-time (curr-time) (days (* -1 EXPIRATION_GRACE_PERIOD))) }
        { "expiryDate":= expiryDate }

        (enforce-one "Address already in use" [
          (enforce (= name "") "Name is an empty string, so mapping doesn't exist")
          (enforce (>= (curr-time) (add-time expiryDate (days EXPIRATION_GRACE_PERIOD))) "Address map exists but domain has expired")
        ])
      )
    )
  )

  (defun get-price (days:integer)
   (if (= days 365) BASE_ONEYEAR_PRICE BASE_TWOYEAR_PRICE)
  )

  (defun set-mappings (fqn:string toplevelname:string address:string)
    (require-capability (MAPPING))
    (write name-map-table fqn {
      "address": address,
      "top-level-name": toplevelname
    })
    (write address-map-table address {
      "name": fqn,
      "top-level-name": toplevelname
    })
  )

  (defun remove-mappings (fqn:string)
    (require-capability (MAPPING))
    (with-read name-map-table fqn
      {
        "address":= address
      }

      (write name-map-table fqn {
        "address": "",
        "top-level-name": ""
      })

      (write address-map-table address {
        "name": "",
        "top-level-name": ""
      })
    )
  )

  (defun register (owner:string address:string name:string nrDays:integer)
    (enforce-domain name)
    (enforce-name-is-valid (strip-domain name))
    (enforce-days-is-valid nrDays)
    (enforce-address-is-valid address)
    (enforce-address-not-in-use address)

    (with-capability (NAME_AVAILABLE name)
    (with-capability (ACCOUNT_GUARD owner)
    (with-capability (NAME_UPSERT name)
    (with-capability (MAPPING)

      (coin.transfer owner VAULT_ACCOUNT (get-price nrDays))

      ; Clear existing subdomain mapping if available
      (with-default-read names-table name
        { "subdomains": [] }
        { "subdomains":= subdomains }


        (map (remove-mappings) subdomains)
      )

      ; write name information
      (write names-table name {
        "owner": owner,
        "lastPrice": (get-price nrDays),
        "expiryDate": (add-time (curr-time) (days nrDays)),
        "subdomains": []
      })

      ; Set mappings
      (set-mappings name name address)

      ;  Clear open sell order if available
      (with-default-read sales-table name
        { "sellable": false }
        { "sellable":= sellable }

        (if sellable (update sales-table name { "sellable": false }) true)
      )
    ))))
  )
)

And this is my name.repl file

;; begin-tx and commit-tx simulate a transaction
(begin-tx "Load modules")

;; set transaction JSON data
(env-data {
  ;; Here we set the required keysets.
  ;; Note:
  ;; - in a real transaction, `admin-key` would be a public key
  ;; - "keys-all" is a built-in predicate that specifies all keys are needed to sign a tx,
  ;;   in this case we only set one key
  'domain-admin-gov-keyset: { "keys": ["domain-admin-gov-keyset"], "pred": "keys-all" },
  'alice-keyset: { "keys": ["alice-key"], "pred": "keys-all" },
  'bob-keyset: { "keys": ["bob-key"], "pred": "keys-all" },
  'namespace-keyset: { "keys": [ ], "pred": "keys-all" },

  ;; Upgrade key is set to false because we are deploying the modules
  'upgrade: false
})


(define-namespace "free" (read-keyset "namespace-keyset") (read-keyset "namespace-keyset"))

;; load fungible-v2 interface
(load "root/fungible-v2.pact")

;; load fungible-xchain-v1 interace
(load "root/fungible-xchain-v1.pact")

;; load coin module
(load "root/coin-v5.pact")

;; create coin module tables
(create-table coin.coin-table)
(create-table coin.allocation-table)

;; load election module
(load "name.pact")

;; commit the transaction
(commit-tx)

(begin-tx "Create KDA accounts")

(coin.create-account "k:36990b871267ec4532551e505260806d7f39378cebb5ea2c998c80301c5a100f" (read-keyset "domain-admin-gov-keyset"))
;; create "alice" KDA account
(coin.create-account "alice" (read-keyset "alice-keyset"))
;; create "bob" KDA account
(coin.create-account "bob" (read-keyset "bob-keyset"))
(env-data {'k:["alice-key"]})
(test-capability (coin.CREDIT "alice"))
(coin.credit "alice" (read-keyset 'k) 10000.0) 

(commit-tx)

(begin-tx "Try buying a domain name")
(use free.testingdomain)
(env-sigs [{ "key": "alice-key", "caps": [(coin.TRANSFER "alice" "k:36990b871267ec4532551e505260806d7f39378cebb5ea2c998c80301c5a100f" 1.0), (free.testingdomain.ACCOUNT_GUARD "alice")]}])

(register "alice" "k:2cf3e52a1e9e961257599a5155cc5ef3e836fc8f70b7edf867cfbf45a07d612d" "elijahd.kda" 365)
(expect "Info details of domain name")

(commit-tx)

To run the name.repl file pact> (load "name")

In the name.repl file am trying to test the register function from the smartcontract which always end up throwing errors.

To run the name.repl file to test the smartcontract pact (load "name.repl")

This is the error


"Loading name.repl..."
"Begin Tx 0: Load modules"
"Setting transaction data"
"Namespace defined: free"
"Loading root/fungible-v2.pact..."
"Loaded interface fungible-v2"
"Loading root/fungible-xchain-v1.pact..."
"Loaded interface fungible-xchain-v1"
"Loading root/coin-v5.pact..."
"Loaded module coin, hash b05fAfvjpnvnl_ssUgyTNkOIAN398ecTuTsUlW_RKyg"
"TableCreated"
"TableCreated"
"Loading name.pact..."
"Namespace set to free"
"Keyset defined"
Warning: using deprecated native overload for *: decimal/integer operator overload is deprecated
"Loaded module free.testingdomain, hash -Y3ux3ibsIwB8keMSmX67JYayvYsgsOlxdswa-OZwhY"
"Commit Tx 0: Load modules"
"Begin Tx 1: Create KDA accounts"
root/coin-v5.pact:516:12: Single-key account protocol violation
 at root/coin-v5.pact:516:12: (enforce false "Single-key account protocol violation")
 at root/coin-v5.pact:515:10: (if (native `=`  Compare alike terms for equality, ret... (native `enforce`  Fail transaction with MSG if pu... (native `enforce`  Fail transaction with MSG if pu...)
 at root/coin-v5.pact:513:8: (if (native `=`  Compare alike terms for equality, ret... true (native `if`  Test COND. If true, evaluate THEN. O...)
 at root/coin-v5.pact:510:4: (if (native `validate-principal`  Validate that PRINCI... true (let (r:<ap> (coin.check-reserved "k:36990b871267e...)
 at root/coin-v5.pact:288:4: (enforce-reserved "k:36990b871267ec4532551e505260806d7f39378cebb5ea2...               KeySet {keys: [domain-admin-gov-keyset],pred: keys...) 
  at name.repl:44:0: (create-account "k:36990b871267ec4532551e505260806d7f39378cebb5ea2... KeySet {keys: [domain-admin-gov-keyset],pred: keys...)

I've been struggling with this for some time and any help would be appreciated. Thank you.


Solution

  • You need to change the line where you define the keyset for domain-admin-gov-keyset in the .repl like this:

      'domain-admin-gov-keyset: { "keys": ["36990b871267ec4532551e505260806d7f39378cebb5ea2c998c80301c5a100f"], "pred": "keys-all" },
    

    For k: accounts it needs to follow a standard. The key in the keyset should be the same as the accountname but without the k: and predicate keys-all. See also https://github.com/kadena-io/KIPs/blob/master/kip-0012/kip-0012.md for definition of the standard.