Stacks Eastern Europe

May 178 min read

Новые функции Clarity в обновлении Stacks 2.1

С приближением обновления 2.1 блокчейна Stacks есть множество того, что стоит ожидать. Как разработчик Clarity, я в восторге от множества новых встроенных функций Clarity, которые будут включены в обновление.

Нетерпеливые разработчики могут узнать об этих новых функциях, изучив предложения и запросы на включение в репозиторий stacks-blockchain. Поскольку многие разработчики еще не видели их, я захотел выделить их все в одном месте в этой статье.

Чтобы увидеть исходный код и документацию для функций Clarity, появившихся в версии 2.1, ознакомьтесь с файлом документации в ветке next.

tx-sponsor?

Stacks поддерживает "спонсорские транзакции". Это транзакции, которые, как обычно, подписываются пользователем, за исключением того, что комиссия за транзакцию покрывается "спонсором". Это может обеспечить отличный пользовательский опыт, когда пользователям даже не нужно владеть STX для совершения транзакции.

С tx-sponsor? у вас может быть выстроена особая логика в зависимости от спонсора. Например, у вас может быть контракт, по которому комиссионные STX возмещаются спонсору в другом активе (например, SIP-010 токен).

(define-public (pay-sponsor)
  (match tx-sponsor?
    sponsor (begin
      ;; сделай что-нибудь, если эта транзакция спонсируется, например, возместить с помощью с другого актива
      (try! (ft-transfer? my-token u100 tx-sender sponsor))
      (ok true)
    )
    (ok false)
  )
)

is-in-mainnet

Тут все довольно просто — это константа, которая возвращает true или false в зависимости от того, является ли эта среда основной сетью (mainnet). Текущие контракты должны иметь жестко закодированные флаги, которые необходимо изменить перед развертыванием в других средах.

;; напечатать сообщение, если находимся в тестовой сети
(define-private (testnet-debug (message (string-ascii 32)))
  (if is-in-mainnet message (print message))
)

Преобразование буферов (последовательности байтов) в целые числа​

У нас есть куча подобных функций для преобразования буферов (последовательности байтов) в целые числа. Вы можете создавать целые числа со знаком или без знака, а также конвертировать из кодировки с прямым порядком байтов или обратным порядком байтов.

Доступные функции:

  • buff-to-int-le

  • buff-to-uint-le

  • buff-to-int-be

  • buff-to-uint-be

Одним из действительно полезных механизмов для этого является преобразование случайных хэшей в число и использование его в качестве случайного числа в вашем контракте. Вы можете, например, взять vrf-seed и преобразовать его в число, получив случайные вероятности на основе этого числа.

Используя хэш, вы можете создать функцию «подбрасывания монеты». Эта функция не была тщательно проверена.

(define-constant max-uint-buff 0xffffffffffffffffffffffffffffffff)
(define-constant max-uint (buff-to-uint-be max-uint-buff))
(define-constant half-uint (/ max-uint u2))

;; учитывая «случайный» буфер, возвращается true в половине случаев.
(define-read-only (coin-flip (buffer (buff 16)))
  (<= (buff-to-uint-be buffer) half-uint)
)

Буфер, который вы вводите в эти функции, может иметь длину только 16 символов, поэтому вам придется slice (нарезать) более длинные буферы (например, vrf-seed). Подробнее об этом ниже!

;; перевод vrf-seed в uint
(define-read-only (vrf-to-uint)
  (let
    (
      (vrf-full (unwrap! (get-block-info? vrf-seed block-height) (err u0)))
      (vrf-slice (slice vrf-full u0 u16))
      (vrf (unwrap! (as-max-len? vrf-slice u16) (err u1)))
      (rand-uint (buff-to-uint-be vrf))
    )
    (ok rand-uint)
  )
)

Преобразование строк в целые числа (и обратно)​

Теперь есть встроенные функции для преобразования строк в целые числа и целых чисел в строки.

  • int-to-ascii

  • int-to-utf8

  • string-to-uint

  • string-to-int

Для целочисленного преобразования одна и та же функция работает как с uint, так и с int. То же самое для преобразования строк — вы можете использовать string-ascii и string-utf8.

При преобразовании строки в целое число результатом будет (optional uint). Если вы передаете число, которое не может быть преобразовано, результат будет none.

(int-to-ascii u32) ;; "32"
(string-to-uint "32");; (some u32)
(string-to-uint "not-number") ;; none

Один из примеров, когда это может быть полезно — это когда вам нужно добавить порядковые номера к строкам в списке. Вот функция, которая это делает:

(define-read-only (indexed-list (items (list 10 (string-ascii 32))))
  (let
    (
      (iterator { index: u1, names: (list) })
      (fold-result (try! (fold indexed-list-fold items (ok iterator))))
      (names (get names fold-result))
    )
    (ok names)
  )
)

(define-read-only (indexed-list-fold
    (item (string-ascii 32))
    (iterator-resp (response { index: uint, names: (list 10 (string-ascii 35)) } uint))
  )
  (let
    (
      (iterator (try! iterator-resp))
      (names (unwrap! (as-max-len? (get names iterator) u9) (err u0)))
      (index (get index iterator))
      (name-a (concat (int-to-ascii index) ". "))
      (name-full (concat name-a item))
      ;; поскольку размер нашего списка всего 10, мы знаем, что эта строка не может быть длиннее 35 символов. По умолчанию тип строки будет (string-ascii 74), чтобы учесть максимально возможное целое число.
      (name (unwrap! (as-max-len? name-full u35) (err u1)))
      (new-names (append names name))
    )
    (ok { index: (+ index u1), names: new-names })
  )
)

(try! (indexed-list (list "alice" "bob"))) ;; (list "1. alice" "2. bob")

Slice

slice очень полезная утилита при работе с хэшами, буферами и строками. Эта функция работает как slice в большинстве языков — она выбирает элементы между указанными вами beginning (начальным) и end (конечным) индексами. Это важная утилита, например, при анализе заголовка блока Биткойна.

Раньше можно было использовать только as-max-len?, если вы хотите взять две длинные строки и объединить их в более короткую строку. Например, представьте, что у вас есть два токена A и B. Их name (имя) в сети возвращает строку из 16 символов, но вы хотите создать пул, имя которого автоматически генерируется из их двух имен. Если бы вы попробовали это с as-max-len?, вы бы не получили результата none, если какое-либо из этих имен было бы слишком длинным. С помощью slice вы можете обрезать их имена, если они слишком длинные.

Особенно удобно это использовать, когда вы пытаетесь работать с traits, которые ограничены в длине результата.

Примечание о slice: на данный момент я заметил некоторое не интуитивное поведение в отношении длины результата применения slice. Длина результата slice на самом деле не сократилась до того, что я ожидал. Мне пришлось использовать as-max-len? для выполнения безопасных операций. Я буду держать в курсе, если будут какие-либо изменения.

;; Возьмем две строки и вернем "${a} ${b} Pool" длиной не более 32 символов.
(define-read-only (make-pool-name (name-a (string-ascii 32)) (name-b (string-ascii 32)))
  (let
    (
      (a-short (slice name-a u0 u13))
      (b-short (slice name-b u0 u13))
      (a (concat a-short " "))
      (b (concat b-short " Pool"))
    )
    (concat a b)
  )
)

Компараторы для строк и буферов​

В Clarity уже есть >, >=, < и <= для целых чисел. В версии 2.1 вы также можете использовать со строками и буферами.

(define-read-only (test-string-comparisons)
  (begin
    (asserts! (> "bbb" "aaa") (err u0))
    (asserts! (>= u"bbb" u"bbb") (err u1))
    (asserts! (< 0x01 0x02) (err u2))
    (ok true)
  )
)

Информация об аккаунте STX​

В настоящее время вы можете использовать stx-get-balance, но в версии 2.1 вы можете получить информацию о заблокированном балансе (находящемся в стакинге (stacking)) для данного кошелька. Эта функция возвращает кортеж (tuple) с значениями locked (заблокированные средства), unlock-height (высота (блок) разблокировки) и unlocked (разблокированные (свободные) средства).

(stx-account 'ST000000000000000000002AMW42H)
;; { locked: u0, unlock-height: u0, unlocked: u0 }

Вот простая функция, которая возвращает true, если пользователь в данный момент добавил средства в стакинг:

(define-read-only (is-stacking (account principal))
  (if (> (get locked (stx-account account)) u0)
    true
    false
  )
)

Перевод STX с memo​

Stacks протокол построен на основе учетной записи, поэтому биржи обычно полагаются на пользователей, имеющих уникальное "memo" (примечание) для связывания переводов со своей учетной записью на бирже. Это хорошо работает для ванильных переводов STX (которые поддерживают memo), но биржи не распознают переводы STX с использованием вызова из контракта.

На самом деле биржи столкнулись с проблемой, когда пользователи пытались вывести средства с одной биржи на другую. Вместо того, чтобы совершать десятки отдельных транзакций, биржи предпочли бы использовать "массовую отправку" через вызов контракта. Это работает хорошо, за исключением того, что вы не можете изначально добавить memo. В процессе развития экосистемы, разработали специальный контракт send-many-memo, который print (печатает) memo, а биржи могли бы использовать API для распознавания этих переводов.

Однако использование специального контракта не продуктивно. Новая stx-transfer-memo?функция упростит весь процесс.

(stx-transfer-memo? u1234 tx-sender 'ST000000000000000000002AMW42H 0x010203)

is-standard

Функция is-standard просто проверяет имеет ли данный principal (кошелек) правильный формат для текущей сети. Это обеспечивает дополнительную безопасность — если пользователь отправляет токены пользователю, который никогда не сможет их потратить (в этой сети), это будет плохой результат для всех.

(is-standard 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6) ;; возвращает true в тестовой сети и false в основной сети
(is-standard 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.foo) ;; возвращает true в тестовой сети и false в основной сети
(is-standard 'SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY) ;; возвращает true в основной сети и false в тестовой сети
(is-standard 'SP3X6QWWETNBZWGBK6DRGTR1KX50S74D3433WDGJY.foo) ;; возвращает true в основной сети и false в тестовой сети
(is-standard 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR) ;; возвращает false как в основной сети, так и в тестовой сети

principal-of

principal-of преобразует публичный ключ в стандартный адрес STX. Это уже было встроено в Stacks 2.0, но оно работало с ошибкой в основной сети. В основной сети результат вернет адрес в формате тестовой сети. К сожалению, это сделало эту функцию практически бесполезной. Исправление пришлось ждать до версии 2.1, потому что его исправление технически нарушило бы консенсус сети.

Я обнаружил, что есть масса интересных вариантов использования principal-of. Это позволяет всем видам вне сетевых подписей использовать специфичную для адреса логику в контракте.

Например, представьте контракт для голосования. Контракт отслеживает за что голосуют отдельные пользователи. Это может довольно дорого обойтись при большом количестве голосующих. Вместо этого подписи можно генерировать в оффлайн режиме, а затем отправлять в сеть в рамках одной транзакции.

(define-map votes-map principal uint)

;; отправить список голосов с подписями, сгенерированными вне сети
(define-public (batch-votes (votes (list 20 { signature: (buff 65), proposal: uint })))
  (ok (map process-vote votes))
)

(define-public (process-vote (vote { signature: (buff 65), proposal: uint }))
  (let
    (
      (proposal (get proposal vote))
      (message-hash (sha256 proposal))
      (public-key (unwrap! (secp256k1-recover? message-hash (get signature vote)) (err u0)))
      (voter (unwrap! (principal-of? public-key) (err u1)))
    )
    (map-set votes-map voter proposal)
    (ok true)
  )
)

Основные ссылки

Оригинал статьи

Официальный сайт Stacks

Stacks Eastern Europe

Социальные сети Stacks - Ссылка на соц. сети и сообщества

Share this story