В двух предыдущих статьях мы рассказали о смарт-аккаунтах и о том, как они могут использоваться для проведения аукционов и создания программ лояльности, а также помогают обеспечить прозрачность финансовых инструментов.
Сейчас мы рассмотрим смарт-ассеты и несколько кейсов их применения, включая замораживание активов и создание ограничений на транзакции по заданным адресам.
Смарт-ассеты Waves позволяют пользователям накладывать скрипты на ассеты, следуя той же механике, что и в случае со смарт-аккаунтами. Каждая новая транзакция, созданная с использованием смарт-ассета, будет подтверждена сначала скриптом, и только потом блокчейном.
Стоит отметить следующие отличия смарт-ассетов от смарт-аккаунтов:
- В коде смарт-ассета нельзя осуществить проверку пруфов (о них мы говорили в первой статье).
- В коде смарт-аккаунта можно проверять ExchangeTransaction, только если ваш аккаунт является аккаунтом-матчером. В противном случае проверяется только ордер. В коде смарт-ассета проверить непосредственно ордер нельзя, можно проверять ExchangeTransaction, и из неё уже при необходимости извлекать ордер.
- У смарт-ассета, в отличие от смарт-аккаунта, нет стейта, но мы все же имеем доступ к стейтам аккаунтов из скрипта.
Смарт-ассеты значительно упрощают написание контрактов, делая реализацию многих кейсов лаконичной и элегантной.
Заморозка активов
Чтобы заморозить активы до определенной высоты блока targetHeight, можно просто задать это значение в скрипте следующего смарт-ассета:
let targetHeight = 1500000
height >= targetHeight
height - функция языка, возращающая текущую высоту.
Условие конкретного матчера
Чтобы установить в качестве желаемого конкретный матчер, вы можете задать его адрес в качестве отправителя в скрипте смарт-ассета следующего вида:
match tx {
case t : ExchangeTransaction =>
t.sender == addressFromString("3PJaDyprvekvPXPuAtxrapacuDJopgJRaU3")
case _ => true
}
«Белый список» получателей
Чтобы разрешить отправку токенов только на определенные счета — создать «белый список» получателей — вы можете использовать смарт-ассет со следующей схемой, проверяющей вхождение в список:
match tx {
case t : TransferTransaction =>
let trustedRecipient1 = addressFromString("3P6ms9EotRX8JwSrebeTXYVnzpsGCrKWLv4")
let trustedRecipient2 = addressFromString("3PLZcCJyYQnfWfzhKXRA4rteCQC9J1ewf5K")
let trustedRecipient3 = addressFromString("3PHrS6VNPRtUD8MHkfkmELavL8JnGtSq5sx")
t.recipient == trustedRecipient1 || t.recipient == trustedRecipient2 || t.recipient == trustedRecipient3
case _ => false
}
В целях безопасности и доказуемой завершимости языка список не содержит реализации итератора. Поэтому он задан в качестве набора конкретных элементов.
«Черный список» получателей
Точно так же, для запрета на отправку токенов на определенные счета, вы можете создать «черный список». При этом используется абсолютно такой же смарт-ассет, но с проверкой адреса на отсутствие в черном списке:
match tx {
case t : TransferTransaction =>
let bannedRecipient1 = addressFromString("3P6ms9EotRX8JwSrebeTXYVnzpsGCrKWLv4")
let bannedRecipient2 = addressFromString("3PLZcCJyYQnfWfzhKXRA4rteCQC9J1ewf5K")
let bannedRecipient3 = addressFromString("3PHrS6VNPRtUD8MHkfkmELavL8JnGtSq5sx")
t.recipient != bannedRecipient1 && t.recipient != bannedRecipient2 && t.recipient != bannedRecipient3
case _ => false
}
Отправка с разрешения эмитента
При помощи смарт-ассета можно также установить опцию отправки смарт-ассета только с разрешения эмитента (commitment/debt label). Эмитент выражает свое согласие, разместив ID транзакции в стейте своего аккаунта:
match tx {
case t : TransferTransaction =>
let issuer = extract(addressFromString("3P6ms9EotRX8JwSrebeTXYVnzpsGCrKWLv4"))
#убеждаемся, что в стейте эмитента содержится ID текущей транзакции
isDefined(getInteger(issuer, toBase58String(t.id)))
case _ => false
}
Обмен только на определенные монеты
Cмарт-ассет допускает разрешение обменивать его только на определенные монеты. Например, чтобы разрешить обмен только на биткойны, можно использовать следующий код:
let BTCId = base58'8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS'
match tx {
case t : ExchangeTransaction =>
t.sellOrder.assetPair.priceAsset == BTCId ||
t.sellOrder.assetPair.amountAsset == BTCId
case _ => true
}
Торговля по цене от оракула
В скрипте смарт-ассета можно задать разрешение на торговлю только по цене, зафиксированной в стейте надежного оракула. Вот пример такого скрипта:
let oracle = Address(base58'3PLNmokt22NrSiNvCLvwMUP84LCMJqbXwAD')
let assetId = toBase58String(base58'oWgJN6YGZFtZrV8BWQ1PGktZikgg7jzGmtm16Ktyvjd')
match tx {
#запрещаем передачу ассета
case t: TransferTransaction | MassTransferTransaction => false
case e: ExchangeTransaction =>
#убеждаемся, что торговля происходит по цене, заданной в стейте оракла для этого ассета
let correctPrice = e.price == extract(getInteger(oracle, assetId))
#убеждаемся, что торговля происходит в обмен на WAVES
let correctPriceAsset = !isDefined(e.sellOrder.assetPair.priceAsset)
correctPrice && correctPriceAsset
case _ => true
}
Здесь мы сталкиваемся с неочевидным моментом при проверке ID ассета, с которым осуществляется торговля. Дело в том, что, если ID ассета не определен, значит, речь идет о WAVES. В скрипте мы убеждаемся, что торговля осуществляется в паре с WAVES, именно таким образом.
Фиксированное увеличение цены
Можно установить фиксированную цену смарт-ассета, которая будет пошагово увеличиваться в заданной пропорции. Вот пример скрипта ассета, цена которого будет увеличивается на 5% каждые 1000 блоков:
let startPrice = 10
let startHeight = 1000
let interval = 1000
#на сколько процентов цена увеличивается за один шаг
let raise = 5
match tx {
case t: TransferTransaction | MassTransferTransaction => false
case e: ExchangeTransaction =>
e.price == startPrice + ((height - startHeight) / interval) * (100 + raise) / 100
&& !isDefined(e.sellOrder.assetPair.priceAsset)
case _ => true
}
Интервальный трейдинг
Также, благодаря скрипту, торговлю смарт-ассетом можно ограничить заранее определенными интервалами. Вот пример такого скрипта:
let startHeight = 10000
let interval = 44000
let limit = 1500
match tx {
case t: TransferTransaction | MassTransferTransaction | ExchangeTransaction =>
(height - startHeight) % interval < limit
case _ => true
}
В скрипте мы убеждаемся, что с начала торговли startHeight прошло не более, чем limit интервалов. Длина интервала равна количеству блоков, заданному в поле interval.