Пример ICO на базе Ergo

This page is machine-translated.
Alex Chepurnoy

10 апреля 2019 г.

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

Часть 1. Предварительные сведения

Важным дизайнерским решением в протоколе криптовалюты является определение того, что именно тратит транзакция. Здесь есть две возможности. Первая — это модель на основе UTXO, как в Bitcoin, где транзакция тратит одноразовые контейнеры активов (называемые 'монетами' или UTXO в Bitcoin) и создает новые. Вторая — это модель на основе аккаунтов, как в Nxt, Ethereum или Waves, где транзакция переводит определенную сумму активов с существующего долгоживущего аккаунта на другой, возможно новый, долгоживущий аккаунт, с возможными побочными эффектами, такими как выполнение контракта в Waves или Ethereum. В этом отношении Ergo похож на Bitcoin, потому что использует подход на основе UTXO, где одноразовые контейнеры, называемые коробками, тратятся. Интересно, что транзакция Ergo также может иметь входные данные, которые не тратятся, а используются для предоставления некоторой информации из текущего набора неиспользованных коробок.

Создать ICO на основе модели UTXO не тривиально, потому что, в отличие от моделей на основе аккаунтов, здесь нет явного постоянного хранилища. Однако Ergo вводит транзакцию расходования в контекст выполнения скрипта.
С этим небольшим изменением становится возможным выразить зависимости между выходами и входами транзакции. В свою очередь, устанавливая зависимости, мы можем выполнять даже произвольно сложные программы, полные Тьюринга, на блокчейне (см. статью "Самовоспроизводящиеся монеты как универсальная машина Тьюринга"). В этой статье мы определим конкретный сценарий многоступенчатого контракта, используя ICO, где у нас есть три этапа (финансирование, выпуск токенов, вывод).

Теперь представьте ICO для тысяч участников. В отличие от Ethereum, Ergo не предоставляет возможности хранить большие наборы данных и переносить их на протяжении выполнения контракта. Скорее, он позволяет хранить только около 40 байт заголовка структуры данных, представленной в виде словаря ключ -> значение, аутентифицированного аналогично дереву Меркла. Чтобы получить доступ к некоторым элементам в словаре или изменить его, транзакция расходования, которая инициирует выполнение защищающего скрипта, должна предоставить доказательства поиска или модификации. Это дает возможность контракту аутентифицировать потенциально огромные наборы данных, не требуя много памяти для хранения состояния контракта. Однако хранение пространства в состоянии (активных контрактах) означало бы более крупные транзакции, но эта проблема легче с точки зрения масштабируемости, а масштабируемость является высшим приоритетом для Ergo.

Часть 2. Контракт ICO

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

  • Во-первых, происходит эпоха финансирования. Она начинается с коробки проекта, аутентифицирующей пустой словарь. Словарь предназначен для хранения пар (инвестор, баланс), где инвестор — это скрипт, защищающий коробку, содержащую выведенные токены. Для баланса мы предполагаем, что 1 токен равен 1 Ergo во время ICO. В течение эпохи финансирования возможно только вложение Ergs в коробку проекта.
    Транзакция финансирования тратит коробку проекта и создает новую коробку проекта с обновленной информацией. Для этого транзакция расходования для коробки проекта также имеет другие входы, которые содержат скрипты вывода инвесторов. Скрипты инвесторов и значения входов должны быть добавлены в дерево новой коробки. Может быть много связанных транзакций финансирования.
  • Во-вторых, период финансирования заканчивается, после чего дерево, содержащее данные инвесторов, становится доступным только для чтения. Аутентифицированное дерево может иметь различные операции модификации, разрешенные индивидуально: вставки, удаления, обновления, или все операции могут быть запрещены (так что дерево может находиться в режиме только для чтения). Также эта транзакция создает токены проекта ICO, которые будут выведены на следующем этапе. Проект может вывести Ergs на этом этапе.
  • В-третьих, инвесторы выводят свои токены ICO. Для этого транзакция расходования создает выходы с условиями защиты и значениями токенов, взятыми из дерева. Выведенные пары также очищаются из дерева. Может быть много связанных транзакций расходования.

Эти три этапа должны быть связаны друг с другом в логическом порядке. Последовательность коробок используется для достижения этих целей.

Часть 3. Подробности контракта ICO

Теперь пришло время предоставить детали и код ErgoScript этапов контракта ICO.

Этап финансирования

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

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

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

Транзакция расходования должна уплатить комиссию, в противном случае маловероятно, что она будет включена в блок. Таким образом, контракт финансирования проверяет, что транзакция расходования имеет два выхода (один для себя, другой для уплаты комиссии), комиссия не должна превышать определенный лимит (в нашем примере всего один наноErg), и защищающее предложение должно быть таким, чтобы только майнер мог потратить выход (в нашем примере мы используем просто переменную "feeProp" из среды компиляции, не предоставляя никаких деталей). Этот "feeProp" соответствует стандарту, хотя и не требуется протоколом.

Код ниже обеспечивает выполнение описанных выше условий. Обратите внимание, что переменная окружения
"nextStageScriptHash" содержит хэш сериализованного скрипта этапа выпуска.

    val selfIndexIsZero = INPUTS(0).id == SELF.id

    val proof = getVar[Coll[Byte]](1).get

    val inputsCount = INPUTS.size

    val toAdd: Coll[(Coll[Byte], Coll[Byte])] = INPUTS.slice(1, inputsCount).map({ (b: Box) =>
        val pk = b.R4[Coll[Byte]].get
        val value = longToByteArray(b.value)
        (pk, value)
    })

    val modifiedTree = SELF.R5[AvlTree].get.insert(toAdd, proof).get

    val expectedTree = OUTPUTS(0).R5[AvlTree].get

    val properTreeModification = modifiedTree == expectedTree

    val outputsCount = OUTPUTS.size == 2

    val selfOutputCorrect = if(HEIGHT < 2000) {
        OUTPUTS(0).propositionBytes == SELF.propositionBytes
    } else {
        blake2b256(OUTPUTS(0).propositionBytes) == nextStageScriptHash
    }

    val feeOutputCorrect = (OUTPUTS(1).value <= 1) && (OUTPUTS(1).propositionBytes == feeBytes)

    val outputsCorrect = outputsCount && feeOutputCorrect && selfOutputCorrect

    selfIndexIsZero && outputsCorrect && properTreeModification

Этап выпуска

На этом этапе есть только одна транзакция расходования, чтобы перейти к следующему этапу (этапу вывода). Транзакция расходования вносит следующие изменения. Во-первых, она изменяет список разрешенных операций в словаре с "только вставки" на "только удаления", так как следующий этап (вывод) только занимается удалением записей из словаря.

Во-вторых, контракт проверяет, что правильное количество токенов ICO выпущено. В Ergo разрешено выпускать один новый вид токена за транзакцию, и идентификатор токена должен быть равен (уникальному) идентификатору первой входной коробки. Подконтракт выпуска проверяет, что новый токен был выпущен, и его количество равно количеству наноErgs, собранных ICO на данный момент.

В-третьих, контракт проверяет, что транзакция расходования действительно воссоздает коробку с защищающим скриптом, соответствующим следующему этапу, этапу вывода.

Наконец, проект должен вывести собранные Ergs, и, конечно, каждая транзакция расходования должна уплатить комиссию. Таким образом, подконтракт проверяет, что транзакция расходования действительно имеет 3 выхода (по одному для коробки токенов проекта, коробки вывода Ergs и коробки комиссии), и что первый выход и выход несут выпущенные токены. Поскольку мы не указываем детали вывода средств проекта, мы требуем подпись проекта на транзакции расходования.

    val openTree = SELF.R5[AvlTree].get
    
    val closedTree = OUTPUTS(0).R5[AvlTree].get
    
    val digestPreserved = openTree.digest == closedTree.digest
    val keyLengthPreserved = openTree.keyLength == closedTree.keyLength
    val valueLengthPreserved = openTree.valueLengthOpt == closedTree.valueLengthOpt
    val treeIsClosed = closedTree.enabledOperations == 4
    
    val tokenId: Coll[Byte] = INPUTS(0).id
    
    val tokensIssued = OUTPUTS(0).tokens(0)._2
    
    val outputsCountCorrect = OUTPUTS.size == 3
    val secondOutputNoTokens = OUTPUTS(0).tokens.size == 1 && OUTPUTS(1).tokens.size == 0 && OUTPUTS(2).tokens.size == 0
    
    val correctTokensIssued = SELF.value == tokensIssued
    
    val correctTokenId = OUTPUTS(0).R4[Coll[Byte]].get == tokenId && OUTPUTS(0).tokens(0)._1 == tokenId
    
    val valuePreserved = outputsCountCorrect && secondOutputNoTokens && correctTokensIssued && correctTokenId
    val stateChanged = blake2b256(OUTPUTS(0).propositionBytes) == nextStageScriptHash
    
    val treeIsCorrect = digestPreserved && valueLengthPreserved && keyLengthPreserved && treeIsClosed
    
    projectPubKey && treeIsCorrect && valuePreserved && stateChanged

Этап вывода

На этом этапе инвесторы могут выводить токены проекта, защищенные предопределенным защищающим скриптом (хэш которого хранится в словаре). Предположим, что вывод осуществляется партиями размером N. Транзакция вывода, таким образом, имеет N + 2 выхода, где первый выход переносит подконтракт вывода и баланс токенов, последний выход оплачивает комиссию, а оставшиеся N выходов имеют защищающие скрипты и значения токенов в соответствии со словарем. Контракт требует два доказательства для элементов словаря: одно, подтверждающее, что значения, подлежащие выводу, действительно находятся в словаре, и второе, подтверждающее, что результирующий словарь не содержит выведенных значений. Подконтракт приведен ниже.

    val removeProof = getVar[Coll[Byte]](2).get
    val lookupProof = getVar[Coll[Byte]](3).get
    val withdrawIndexes = getVar[Coll[Int]](4).get

    val out0 = OUTPUTS(0)

    val tokenId: Coll[Byte] = SELF.R4[Coll[Byte]].get

    val withdrawals = withdrawIndexes.map({(idx: Int) =>
        val b = OUTPUTS(idx)
        if(b.tokens(0)._1 == tokenId) {
            (blake2b256(b.propositionBytes), b.tokens(0)._2)
        } else {
            (blake2b256(b.propositionBytes), 0L)
        }
    })

    val withdrawValues = withdrawals.map({(t: (Coll[Byte], Long)) => t._2})

    val withdrawTotal = withdrawValues.fold(0L, { (l1: Long, l2: Long) => l1 + l2 })

    val toRemove = withdrawals.map({(t: (Coll[Byte], Long)) => t._1})

    val initialTree = SELF.R5[AvlTree].get

    val removedValues = initialTree.getMany(toRemove, lookupProof).map({(o: Option[Coll[Byte]]) => byteArrayToLong(o.get)})
    val valuesCorrect = removedValues == withdrawValues

    val modifiedTree = initialTree.remove(toRemove, removeProof).get

    val expectedTree = out0.R5[AvlTree].get

    val selfTokensCorrect = SELF.tokens(0)._1 == tokenId
    val selfOutTokensAmount = SELF.tokens(0)._2
    val soutTokensCorrect = out0.tokens(0)._1 == tokenId
    val soutTokensAmount = out0.tokens(0)._2

    val tokensPreserved = selfTokensCorrect && soutTokensCorrect && (soutTokensAmount + withdrawTotal == selfOutTokensAmount)

    val properTreeModification = modifiedTree == expectedTree

    val selfOutputCorrect = out0.propositionBytes == SELF.propositionBytes

    properTreeModification && valuesCorrect && selfOutputCorrect && tokensPreserved

Возможные улучшения

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

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

Share post

Ergo Infrastructure DAO: Децентрализация основного каркаса экосистемы Ergo

Ergo Infrastructure DAO: Децентрализация основного каркаса экосистемы Ergo

Миссия Ergo всегда была основана на децентрализации, не только на уровне консенсуса, но и на всем стеке.

Ergo Platform

13 августа 2025 г.

Mew Finance: Игровой DeFi инструмент для экосистемы Ergo

Mew Finance: Игровой DeFi инструмент для экосистемы Ergo

Mew Finance — это децентрализованный набор приложений на блокчейне Ergo.

Ergo Platform

12 августа 2025 г.

Lithos: Децентрализация майнинга с помощью ончейн пулов

Lithos: Децентрализация майнинга с помощью ончейн пулов

Lithos — это новый протокол, разработанный для изменения работы майнинг-пулов, перемещая их в ончейн, предоставляя майнерам полный.

Ergo Platform

24 июля 2025 г.

Sigma 6.0: Более умный и гибкий Ergo

Sigma 6.0: Более умный и гибкий Ergo

Sigma 6.0 — это значительное предложенное обновление для блокчейна Ergo.

Ergo Platform

23 июля 2025 г.

Формирование будущего Rosen: Общественный призыв к пяти ключевым предложениям казначейства

Формирование будущего Rosen: Общественный призыв к пяти ключевым предложениям казначейства

Соучредитель Rosen, Armeanio, представил пять новых предложений в казначейство Rosen.

Ergo Platform

9 июля 2025 г.

Расширенный UTXO Ergo и восход искусственного экономического интеллекта

Расширенный UTXO Ergo и восход искусственного экономического интеллекта

Практическое видение автономных экономических агентов Автономные экономические агенты на блокчейне Ergo выполняют полезную работу.

Ergo Platform

12 мая 2025 г.

ErgoHACK X: Искусственный Интеллект на Блокчейне Ergo

ErgoHACK X: Искусственный Интеллект на Блокчейне Ergo

Празднование Десятилетия Децентрализованных Инноваций Присоединяйтесь к 10-летнему юбилею ErgoHACK и будьте на переднем крае револ.

Ergo Platform

10 апреля 2025 г.