บทเรียนที่ 1

Введение и мультисиговые контракты

Контракты с несколькими подписями (multisig), также известные как контракты "M-of-N", являются важнейшим механизмом, используемым для повышения безопасности и гибкости транзакций в среде блокчейн. Эти контракты изменяют способ контроля над активами, требуя одобрения нескольких сторон перед совершением сделок. Термин "M-of-N" относится к требованию, что M из N всех сторон должны одобрить транзакцию, чтобы она была действительной.

Теория мультисиговых контрактов

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

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

Мультисиговые контракты можно представить себе как цифровой эквивалент сейфа, для открытия которого требуется несколько ключей. Общее количество ключей (N) и минимальное количество ключей, необходимое для открытия ящика (M), оговариваются при создании контракта.

Контракты Multisig могут иметь множество различных конфигураций в зависимости от значений M и N:

  • 1-of-N: Единственная сторона из общего числа может одобрить транзакцию. Эта конфигурация аналогична обычной транзакции без multisig. Он может использоваться там, где для удобства существует несколько ключей, но любой из них может утверждать транзакции.
  • N-of-N: Все стороны должны одобрить сделку. Такая конфигурация обеспечивает наивысший уровень безопасности, но может стать проблематичной, если одна из сторон потеряет свой ключ или откажется утверждать транзакции.
  • M-of-N (где M < N): Подмножество всех сторон должно одобрить транзакцию. Такая конфигурация часто используется на практике, поскольку она обеспечивает баланс между безопасностью и гибкостью.

Мультисиговые контракты в Blockchain

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

  • Кошельки: Мультисиговые кошельки используются для защиты активов. Они требуют подписания транзакций несколькими сторонами, обеспечивая тем самым дополнительную защиту от краж, внешних взломов и внутренних угроз.
  • Децентрализованные автономные организации (ДАО): ДАО часто используют мультисиговые контракты для обеспечения соблюдения своих правил управления. Голосование по предложениям осуществляется в виде мультисиговых транзакций, в которых члены DAO выступают в качестве подписантов. Предложение выполняется только в том случае, если оно получает достаточное количество голосов.
  • Межцепочечные операции: В межцепочечных операциях мультисиговые контракты могут выступать в качестве хранителей активов. Когда активы перемещаются с одной блокчейн-цепи на другую, мультисигнальный контракт на исходной цепи может гарантировать, что активы будут надежно заперты до тех пор, пока операция на другой цепи не будет подтверждена.
    Хотя реализация мультисиговых контрактов может варьироваться от одного блокчейна к другому, основная концепция остается неизменной - необходимость одобрения транзакции несколькими сторонами до ее выполнения. Этот дополнительный уровень безопасности делает мультисиговые контракты незаменимым инструментом в блокчейне и криптовалютном пространстве.

Пример кодирования: Написание и развертывание мультисиговых контрактов с помощью SmartPy

Что касается наших примеров кода, то мы рассмотрим три различные реализации контрактов с несколькими подписями:

Контракт Лямбда

Он достаточно универсален и позволяет использовать его в широком диапазоне. Для выполнения произвольных лямбда-функций требуется несколько сигнатур.

Python
import smartpy as sp


@sp.module
def main():
 operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_operations=True)

 class MultisigLambda(sp.Contract):
      """Многочленное голосование за выполнение лямбд.

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

        Когда лямбда применяется, все представленные до этого момента лямбды инактивируются.
        Участники все еще могут представлять новые лямбды.
        """

       def __init__(self, members, required_votes):            Конструктор Args: members (sp.set of sp.address): люди, которые могут подавать заявки и голосовать для лямбды.               
 """
                required_votes (sp.nat): количество необходимых голосов
 " " " 
 assert required_votes <= sp.len(
 members
 ), "required_votes must be <= len(members)"
 self.data.lambdas = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, operation_lambda]
 )
 self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]]
 )
 self.data.nextId = 0
 self.data.inactiveBefore = 0
 self.data.members = sp.cast(members, sp.set[sp.address])
            self.data.required_votes = sp.cast(required_votes, sp.nat)

       @sp.entrypoint
 def submit_lambda(self, lambda_):
          """Отправьте новую лямбду на голосование.

            Подача предложения не означает голосования за него.

            Args:
 lambda_(sp.lambda с операциями): лямбда, предложенная для голосования.
            Возникает:
               `Вы не являетесь членом клуба`
 " " " 
 assert self.data.members.contains(sp.sender), "Вы не являетесь участником"
 self.data.lambdas[self.data.nextId] = lambda_
 self.data.votes[self.data.nextId] = sp.set()
            self.data.nextId += 1

 @sp.entrypoint
 def vote_lambda(self, id):
          """Проголосуйте за лямбду.

            Args:
 id(sp.nat): id лямбды, за которую нужно голосовать.
            Повышает:
               `Вы не являетесь участником`, `Лямбда неактивна`, `Лямбда не найдена`

 Голосование против или за не проводится. Если кто-то не согласен с лямбдой
, он может не голосовать.
            """
           assert self.data.members.contains(sp.sender), "Вы не являетесь членом клуба"
 assert id >= self.data.inactiveBefore, "Лямбда неактивна"
 assert self.data.lambdas.contains(id), "Лямбда не найдена"
 self.data.votes[id].add(sp.sender)
            if sp.len(self.data.votes[id]) >= self.data.required_votes:
                self.data.lambdas[id]()
                self.data.inactiveBefore = self.data.nextId

 @sp.onchain_view()
 def get_lambda(self, id):
          """Возвращает соответствующую лямбду.

            Args:
 id (sp.nat): id лямбды, которую нужно получить.

            Возврат:
 пара из лямбды и логического выражения, показывающего, активна ли лямбда.
            """
           return (self.data.lambdas[id], id >= self.data.inactiveBefore)


# if "templates" not in __name__:


@sp.module
def test():
 class Administrated(sp.Contract):
 def __init__(self, admin):
 self.data.admin = admin
 self.data.value = sp.int(0)

        @sp.entrypoint
 def set_value(self, value):
 assert sp.sender == self.data.admin
 self.data.value = value


@sp.add_test(name="Базовый сценарий MultisigLambda", is_default=True)
def basic_scenario():
  """Используйте multisigLambda в качестве администратора примера контракта.

    Тесты:
 - Origination
 - Lambda submission
 - Lambda vote
 " " " 
 sc = sp.test_scenario([main, test])
 sc.h1("Базовый сценарий.")

    member1 = sp.test_account("member1")
    member2 = sp.test_account("member2")
    member3 = sp.test_account("member3")
    members = sp.set([member1.address, member2.address, member3.address])

    sc.h2("MultisigLambda: origination")
 c1 = main.MultisigLambda(members, 2)
 sc += c1

 sc.h2("Administrated: origination")
 c2 = test.Administrated(c1.address)
    sc += c2

 sc.h2("MultisigLambda: submit_lambda")

 def set_42(params):
 administrated = sp.contract(sp.TInt, c2.address, entrypoint="set_value")
 sp.transfer(sp.int(42), sp.tez(0), administrated.open_some())

    lambda_ = sp.build_lambda(set_42, with_operations=True)
 c1.submit_lambda(lambda_).run(sender=member1)

 sc.h2("MultisigLambda: vote_lambda")
 c1.vote_lambda(0).run(sender=member1)
    c1.vote_lambda(0).run(sender=member2)

    # Мы можем проверить, что администрируемый контракт получил перевод.
    sc.verify(c2.data.value == 42)

Контракт MultisigAction

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

Python
import smartpy as sp


@sp.module
def main():
   # Спецификация типа действия внутреннего администрирования
 InternalAdminAction: type = sp.variant(
        addSigners=sp.list[sp.address],
        changeQuorum=sp.nat,
        removeSigners=sp.list[sp.address],
    )

 класс MultisigAction(sp.Contract):
      """Контракт, который может быть использован несколькими подписантами для администрирования других контрактов
. Администрируемые контракты реализуют интерфейс, который делает
 возможным эксплицировать процесс администрирования для пользователей, не являющихся экспертами.

        Подписавшиеся голосуют за предложения. Предложение - это список цели с перечнем действий
. Действие - это простой байт, но он предназначен для того, чтобы быть пакетным значением
 варианта. Этот простой паттерн позволяет построить UX-интерфейс
, отображающий содержание предложения, или построить его.
        """

       def __init__(self, quorum, signers): self.data.inactiveBefore = 0
 self.data.nextId = 0
 self.data.proposals = sp.cast(
                sp.big_map(),
 sp.big_map[
 sp.nat,
 sp.list[sp.record(target=sp.address, actions=sp.list[sp.bytes])],
                ],
 )
 self.data.quorum = sp.cast(quorum, sp.nat)
 self.data.signers = sp.cast(signers, sp.set[sp.address])
            self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]]
 )

       @sp.entrypoint
 def send_proposal(self, proposal):
          """Только для подписчиков. Подайте предложение на голосование.

            Args:
 предложение (sp.список sp.запись целевого адреса и действия): Список\
 цели и связанных с ней административных действий.
            """
           assert self.data.signers.contains(sp.sender), "Только подписавшие могут предлагать"
 self.data.proposals[self.data.nextId]. = предложение
 self.data.votes[self.data.nextId] = sp.set()
            self.data.nextId += 1

 @sp.entrypoint
 def vote(self, pId):
          """Проголосуйте за одно или несколько предложений

 Args:
 pId (sp.nat): Id предложения.
            """
           assert self.data.signers.contains(sp.sender), "Только подписавшие могут голосовать"
 assert self.data.votes.contains(pId), "Предложение неизвестно"
 assert pId >= self.data.inactiveBefore, "Предложение неактивно"
 self.data.votes[pId].add(sp.sender)

            if sp.len(self.data.votes.get(pId, default=sp.set())) >= self.data.quorum:
                self._onApproved(pId)

        @sp.private(with_storage="read-write", with_operations=True)
 def _onApproved(self, pId):
          """Вложенная функция. Логика, применяемая, когда предложение было одобрено."""
            proposal = self.data.proposals.get(pId, default=[])
 for p_item in proposal:
 contract = sp.contract(sp.list[sp.bytes], p_item.target)
                sp.transfer(
 p_item.actions,
                    sp.tez(0),
 contract.unwrap_some(error="InvalidTarget"),
                )
           # Инактивируйте все предложения, которые уже были поданы.
            self.data.inactiveBefore = self.data.nextId

 @sp.entrypoint
 def administrate(self, actions):
          """Только самостоятельный вызов. Администрируйте этот контракт.

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

            Args:
 действия (sp.список sp.байтов): Список упакованных вариантов \
 `InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`).
            """
           assert ( sp.sender == sp.self_address() ), Эта точка входа должна быть вызвана через систему предложений.  for packed_actions in actions: action = sp.unpack(packed_actions,

 "" InternalAdminAction).unwrap_some(
 error="Bad actions format"
 )
 with sp.match(action):
 with sp.case.changeQuorum as quorum:
 self.data.quorum = quorum
 with sp.case.addSigners as added:
 for signer in added:
 self.data.signers.add(signer)
                    с sp.case.removeSigners as removed:
 for address in removed:
 self.data.signers.remove(address)
                # Убедитесь, что контракт никогда не требует кворума больше, чем общее количество подписавших его лиц.
                assert self.data.quorum <= sp.len(
 self.data.signers
 ), "Больше кворума, чем подписавших."


if "templates" not in __name__:

   @sp.add_test(name="Основной сценарий", is_default=True)
 def test():
 signer1 = sp.test_account("signer1")
        signer2 = sp.test_account("signer2")
        signer3 = sp.test_account("signer3")

        s = sp.test_scenario(main)
        s.h1("Базовый сценарий")

 s.h2("Оригинал")
 c1 = main.MultisigAction(
 quorum=2,
 signers=sp.set([signer1.address, signer2.address]),
 )
 s += c1

 s.h2("Предложение для добавления нового подписанта")
 target = sp.to_address(
            sp.contract(sp.TList(sp.TBytes), c1.address, "administrate").open_some()
 )
 action = sp.pack(
            sp.set_type_expr(
 sp.variant("addSigners", [signer3.address]), main.InternalAdminAction
 )
 )
 c1.send_proposal([sp.record(target=target, actions=[action])].run(
 sender=signer1
 )

 s.h2("Signer 1 голосует за предложение")
 c1.vote(0).run(sender=signer1)
        s.h2("Подписант 2 голосует за предложение")
 c1.vote(0).run(sender=signer2)

        s.verify(c1.data.signers.contains(signer3.address))

Контракт MultisigView

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

Python
import smartpy as sp


@sp.module
def main():
 class MultisigView(sp.Contract):
      """Многочлены голосуют за произвольные байты.

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

        Любой байт, набравший необходимое количество голосов, может быть подтвержден с помощью представления.
        """

       def __init__(self, members, required_votes):            Конструктор Args: members (sp.set of sp.address): люди, которые могут подавать заявки и голосовать за lambda.               
 """
                required_votes (sp.nat): количество необходимых голосов
 " " " 
 assert required_votes <= sp.len(
 members
 ), "required_votes must be <= len(members)"
 self.data.proposals = sp.cast(sp.big_map(), sp.big_map[sp.bytes, sp.bool])
 self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.bytes, sp.set[sp.address]]
 )
 self.data.members = sp.cast(members, sp.set[sp.address])
            self.data.required_votes = sp.cast(required_votes, sp.nat)

       @sp.entrypoint
 def submit_proposal(self, bytes):
          """Отправьте новое предложение на голосование.

            Подача предложения не означает голосования за него.

            Args:
 bytes(sp.bytes): байты, предложенные для голосования.
            Возникает:
               `Вы не являетесь членом клуба`
 " " " 
 assert self.data.members.contains(sp.sender), "Вы не являетесь участником"
 self.data.proposals[bytes] = False
 self.data.votes[bytes] = sp.set()

        @sp.entrypoint
 def vote_proposal(self, bytes):
          """Проголосуйте за предложение.

            Голосование против или за не проводится. Если кто-то не согласен с предложением, он
 может не голосовать. Предупреждение: старые предложения, не прошедшие голосование, никогда не становятся
 устаревшими.

            Args:
 id(sp.bytes): байты предложения.
            Возникают:
               `Вы не являетесь членом клуба`, `Предложение не найдено`
 " " " 
 assert self.data.members.contains(sp.sender), "Вы не являетесь участником"
 assert self.data.proposals.contains(bytes), "Предложение не найдено"
 self.data.votes[bytes].add(sp.sender)
            if sp.len(self.data.votes[bytes]) >= self.data.required_votes:
                self.data.proposals[bytes] = True

 @sp.onchain_view()
 def is_voted(self, id):
          """Возвращает логическое число, указывающее, было ли предложение поставлено на голосование.

            Args:
 id (sp.bytes): байты предложения
 Return:
 (sp.bool): True, если предложение было проголосовано, False в противном случае.
            """
           return self.data.proposals.get(id, error="Предложение не найдено")


if "templates" not in __name__:

   @sp.add_test(name="Базовый сценарий MultisigView", is_default=True)
 def basic_scenario():
      """Сценарий с голосованием по контракту multisigView.

        Тесты:
 - Origination
 - Proposal submission
 - Proposal vote
 " " " 
 sc = sp.test_scenario(main)
        sc.h1("Базовый сценарий.")

        member1 = sp.test_account("member1")
        member2 = sp.test_account("member2")
        member3 = sp.test_account("member3")
        members = sp.set([member1.address, member2.address, member3.address])

        sc.h2("Origination")
 c1 = main.MultisigView(members, 2)
 sc += c1

 sc.h2("submit_proposal")
 c1.submit_proposal(sp.bytes("0x42")).run(sender=member1)

 sc.h2("vote_proposal")
 c1.vote_proposal(sp.bytes("0x42")).run(sender=member1)
 c1.vote_proposal(sp.bytes("0x42")).run(sender=member2)

 # Мы можем проверить, что предложение было подтверждено.
        sc.verify(c1.is_voted(sp.bytes("0x42")))

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

Пошаговое руководство по опробованию Multisig-контракта на SmartPy Online

Чтобы опробовать мультисигмальные контракты, которые мы написали на SmartPy, Вы можете выполнить следующие шаги:

  1. Перейдите в среду разработки SmartPy IDE по адресу https://smartpy.io/ide.

  2. Вставьте код контракта в редактор. Вы можете заменить существующий код.

  3. Чтобы выполнить контракт, щелкните на кнопке "Выполнить", расположенной на верхней панели.

  4. После запуска контракта Вы можете просмотреть выполнение сценария на панели "Output" справа. Здесь Вы можете просмотреть детали каждого действия, включая предложения, голосования и одобрения.

  5. Чтобы развернуть свой контракт в сети Tezos, Вам сначала нужно скомпилировать его. Нажмите кнопку "Compile" на верхней панели.

  6. После компиляции Вы можете развернуть контракт в тестовой сети, нажав кнопку "Развернуть контракт Michelson". Вам нужно будет предоставить Секретный ключ для счета Tezos, на котором достаточно средств для оплаты расходов на газ при развертывании.

  7. Как только контракт будет развернут, Вам будет предоставлен адрес контракта в блокчейне. Вы можете использовать этот адрес для взаимодействия с контрактом посредством транзакций.

  8. Для подачи предложений или голосования в контрактах Вы можете использовать точки входа, определенные в коде контракта, такие как submit_proposal или vote_proposal. Их можно вызывать непосредственно из транзакций, которые Вы создаете.

Помните, что хотя SmartPy IDE позволяет Вам протестировать Ваш контракт на смоделированном блокчейне, развертывание контракта в реальной сети Tezos повлечет за собой расходы на газ, которые должны быть оплачены в XTZ, родной криптовалюте сети Tezos.

ข้อจำกัดความรับผิด
* การลงทุนคริปโตมีความเสี่ยงสูง โปรดดำเนินการด้วยความระมัดระวัง หลักสูตรนี้ไม่ได้มีไว้เพื่อเป็นคำแนะนำในการลงทุน
* หลักสูตรนี้สร้างขึ้นโดยผู้เขียนที่ได้เข้าร่วม Gate Learn ความคิดเห็นของผู้เขียนไม่ได้มาจาก Gate Learn
แคตตาล็อก
บทเรียนที่ 1

Введение и мультисиговые контракты

Контракты с несколькими подписями (multisig), также известные как контракты "M-of-N", являются важнейшим механизмом, используемым для повышения безопасности и гибкости транзакций в среде блокчейн. Эти контракты изменяют способ контроля над активами, требуя одобрения нескольких сторон перед совершением сделок. Термин "M-of-N" относится к требованию, что M из N всех сторон должны одобрить транзакцию, чтобы она была действительной.

Теория мультисиговых контрактов

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

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

Мультисиговые контракты можно представить себе как цифровой эквивалент сейфа, для открытия которого требуется несколько ключей. Общее количество ключей (N) и минимальное количество ключей, необходимое для открытия ящика (M), оговариваются при создании контракта.

Контракты Multisig могут иметь множество различных конфигураций в зависимости от значений M и N:

  • 1-of-N: Единственная сторона из общего числа может одобрить транзакцию. Эта конфигурация аналогична обычной транзакции без multisig. Он может использоваться там, где для удобства существует несколько ключей, но любой из них может утверждать транзакции.
  • N-of-N: Все стороны должны одобрить сделку. Такая конфигурация обеспечивает наивысший уровень безопасности, но может стать проблематичной, если одна из сторон потеряет свой ключ или откажется утверждать транзакции.
  • M-of-N (где M < N): Подмножество всех сторон должно одобрить транзакцию. Такая конфигурация часто используется на практике, поскольку она обеспечивает баланс между безопасностью и гибкостью.

Мультисиговые контракты в Blockchain

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

  • Кошельки: Мультисиговые кошельки используются для защиты активов. Они требуют подписания транзакций несколькими сторонами, обеспечивая тем самым дополнительную защиту от краж, внешних взломов и внутренних угроз.
  • Децентрализованные автономные организации (ДАО): ДАО часто используют мультисиговые контракты для обеспечения соблюдения своих правил управления. Голосование по предложениям осуществляется в виде мультисиговых транзакций, в которых члены DAO выступают в качестве подписантов. Предложение выполняется только в том случае, если оно получает достаточное количество голосов.
  • Межцепочечные операции: В межцепочечных операциях мультисиговые контракты могут выступать в качестве хранителей активов. Когда активы перемещаются с одной блокчейн-цепи на другую, мультисигнальный контракт на исходной цепи может гарантировать, что активы будут надежно заперты до тех пор, пока операция на другой цепи не будет подтверждена.
    Хотя реализация мультисиговых контрактов может варьироваться от одного блокчейна к другому, основная концепция остается неизменной - необходимость одобрения транзакции несколькими сторонами до ее выполнения. Этот дополнительный уровень безопасности делает мультисиговые контракты незаменимым инструментом в блокчейне и криптовалютном пространстве.

Пример кодирования: Написание и развертывание мультисиговых контрактов с помощью SmartPy

Что касается наших примеров кода, то мы рассмотрим три различные реализации контрактов с несколькими подписями:

Контракт Лямбда

Он достаточно универсален и позволяет использовать его в широком диапазоне. Для выполнения произвольных лямбда-функций требуется несколько сигнатур.

Python
import smartpy as sp


@sp.module
def main():
 operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_operations=True)

 class MultisigLambda(sp.Contract):
      """Многочленное голосование за выполнение лямбд.

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

        Когда лямбда применяется, все представленные до этого момента лямбды инактивируются.
        Участники все еще могут представлять новые лямбды.
        """

       def __init__(self, members, required_votes):            Конструктор Args: members (sp.set of sp.address): люди, которые могут подавать заявки и голосовать для лямбды.               
 """
                required_votes (sp.nat): количество необходимых голосов
 " " " 
 assert required_votes <= sp.len(
 members
 ), "required_votes must be <= len(members)"
 self.data.lambdas = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, operation_lambda]
 )
 self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]]
 )
 self.data.nextId = 0
 self.data.inactiveBefore = 0
 self.data.members = sp.cast(members, sp.set[sp.address])
            self.data.required_votes = sp.cast(required_votes, sp.nat)

       @sp.entrypoint
 def submit_lambda(self, lambda_):
          """Отправьте новую лямбду на голосование.

            Подача предложения не означает голосования за него.

            Args:
 lambda_(sp.lambda с операциями): лямбда, предложенная для голосования.
            Возникает:
               `Вы не являетесь членом клуба`
 " " " 
 assert self.data.members.contains(sp.sender), "Вы не являетесь участником"
 self.data.lambdas[self.data.nextId] = lambda_
 self.data.votes[self.data.nextId] = sp.set()
            self.data.nextId += 1

 @sp.entrypoint
 def vote_lambda(self, id):
          """Проголосуйте за лямбду.

            Args:
 id(sp.nat): id лямбды, за которую нужно голосовать.
            Повышает:
               `Вы не являетесь участником`, `Лямбда неактивна`, `Лямбда не найдена`

 Голосование против или за не проводится. Если кто-то не согласен с лямбдой
, он может не голосовать.
            """
           assert self.data.members.contains(sp.sender), "Вы не являетесь членом клуба"
 assert id >= self.data.inactiveBefore, "Лямбда неактивна"
 assert self.data.lambdas.contains(id), "Лямбда не найдена"
 self.data.votes[id].add(sp.sender)
            if sp.len(self.data.votes[id]) >= self.data.required_votes:
                self.data.lambdas[id]()
                self.data.inactiveBefore = self.data.nextId

 @sp.onchain_view()
 def get_lambda(self, id):
          """Возвращает соответствующую лямбду.

            Args:
 id (sp.nat): id лямбды, которую нужно получить.

            Возврат:
 пара из лямбды и логического выражения, показывающего, активна ли лямбда.
            """
           return (self.data.lambdas[id], id >= self.data.inactiveBefore)


# if "templates" not in __name__:


@sp.module
def test():
 class Administrated(sp.Contract):
 def __init__(self, admin):
 self.data.admin = admin
 self.data.value = sp.int(0)

        @sp.entrypoint
 def set_value(self, value):
 assert sp.sender == self.data.admin
 self.data.value = value


@sp.add_test(name="Базовый сценарий MultisigLambda", is_default=True)
def basic_scenario():
  """Используйте multisigLambda в качестве администратора примера контракта.

    Тесты:
 - Origination
 - Lambda submission
 - Lambda vote
 " " " 
 sc = sp.test_scenario([main, test])
 sc.h1("Базовый сценарий.")

    member1 = sp.test_account("member1")
    member2 = sp.test_account("member2")
    member3 = sp.test_account("member3")
    members = sp.set([member1.address, member2.address, member3.address])

    sc.h2("MultisigLambda: origination")
 c1 = main.MultisigLambda(members, 2)
 sc += c1

 sc.h2("Administrated: origination")
 c2 = test.Administrated(c1.address)
    sc += c2

 sc.h2("MultisigLambda: submit_lambda")

 def set_42(params):
 administrated = sp.contract(sp.TInt, c2.address, entrypoint="set_value")
 sp.transfer(sp.int(42), sp.tez(0), administrated.open_some())

    lambda_ = sp.build_lambda(set_42, with_operations=True)
 c1.submit_lambda(lambda_).run(sender=member1)

 sc.h2("MultisigLambda: vote_lambda")
 c1.vote_lambda(0).run(sender=member1)
    c1.vote_lambda(0).run(sender=member2)

    # Мы можем проверить, что администрируемый контракт получил перевод.
    sc.verify(c2.data.value == 42)

Контракт MultisigAction

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

Python
import smartpy as sp


@sp.module
def main():
   # Спецификация типа действия внутреннего администрирования
 InternalAdminAction: type = sp.variant(
        addSigners=sp.list[sp.address],
        changeQuorum=sp.nat,
        removeSigners=sp.list[sp.address],
    )

 класс MultisigAction(sp.Contract):
      """Контракт, который может быть использован несколькими подписантами для администрирования других контрактов
. Администрируемые контракты реализуют интерфейс, который делает
 возможным эксплицировать процесс администрирования для пользователей, не являющихся экспертами.

        Подписавшиеся голосуют за предложения. Предложение - это список цели с перечнем действий
. Действие - это простой байт, но он предназначен для того, чтобы быть пакетным значением
 варианта. Этот простой паттерн позволяет построить UX-интерфейс
, отображающий содержание предложения, или построить его.
        """

       def __init__(self, quorum, signers): self.data.inactiveBefore = 0
 self.data.nextId = 0
 self.data.proposals = sp.cast(
                sp.big_map(),
 sp.big_map[
 sp.nat,
 sp.list[sp.record(target=sp.address, actions=sp.list[sp.bytes])],
                ],
 )
 self.data.quorum = sp.cast(quorum, sp.nat)
 self.data.signers = sp.cast(signers, sp.set[sp.address])
            self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]]
 )

       @sp.entrypoint
 def send_proposal(self, proposal):
          """Только для подписчиков. Подайте предложение на голосование.

            Args:
 предложение (sp.список sp.запись целевого адреса и действия): Список\
 цели и связанных с ней административных действий.
            """
           assert self.data.signers.contains(sp.sender), "Только подписавшие могут предлагать"
 self.data.proposals[self.data.nextId]. = предложение
 self.data.votes[self.data.nextId] = sp.set()
            self.data.nextId += 1

 @sp.entrypoint
 def vote(self, pId):
          """Проголосуйте за одно или несколько предложений

 Args:
 pId (sp.nat): Id предложения.
            """
           assert self.data.signers.contains(sp.sender), "Только подписавшие могут голосовать"
 assert self.data.votes.contains(pId), "Предложение неизвестно"
 assert pId >= self.data.inactiveBefore, "Предложение неактивно"
 self.data.votes[pId].add(sp.sender)

            if sp.len(self.data.votes.get(pId, default=sp.set())) >= self.data.quorum:
                self._onApproved(pId)

        @sp.private(with_storage="read-write", with_operations=True)
 def _onApproved(self, pId):
          """Вложенная функция. Логика, применяемая, когда предложение было одобрено."""
            proposal = self.data.proposals.get(pId, default=[])
 for p_item in proposal:
 contract = sp.contract(sp.list[sp.bytes], p_item.target)
                sp.transfer(
 p_item.actions,
                    sp.tez(0),
 contract.unwrap_some(error="InvalidTarget"),
                )
           # Инактивируйте все предложения, которые уже были поданы.
            self.data.inactiveBefore = self.data.nextId

 @sp.entrypoint
 def administrate(self, actions):
          """Только самостоятельный вызов. Администрируйте этот контракт.

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

            Args:
 действия (sp.список sp.байтов): Список упакованных вариантов \
 `InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`).
            """
           assert ( sp.sender == sp.self_address() ), Эта точка входа должна быть вызвана через систему предложений.  for packed_actions in actions: action = sp.unpack(packed_actions,

 "" InternalAdminAction).unwrap_some(
 error="Bad actions format"
 )
 with sp.match(action):
 with sp.case.changeQuorum as quorum:
 self.data.quorum = quorum
 with sp.case.addSigners as added:
 for signer in added:
 self.data.signers.add(signer)
                    с sp.case.removeSigners as removed:
 for address in removed:
 self.data.signers.remove(address)
                # Убедитесь, что контракт никогда не требует кворума больше, чем общее количество подписавших его лиц.
                assert self.data.quorum <= sp.len(
 self.data.signers
 ), "Больше кворума, чем подписавших."


if "templates" not in __name__:

   @sp.add_test(name="Основной сценарий", is_default=True)
 def test():
 signer1 = sp.test_account("signer1")
        signer2 = sp.test_account("signer2")
        signer3 = sp.test_account("signer3")

        s = sp.test_scenario(main)
        s.h1("Базовый сценарий")

 s.h2("Оригинал")
 c1 = main.MultisigAction(
 quorum=2,
 signers=sp.set([signer1.address, signer2.address]),
 )
 s += c1

 s.h2("Предложение для добавления нового подписанта")
 target = sp.to_address(
            sp.contract(sp.TList(sp.TBytes), c1.address, "administrate").open_some()
 )
 action = sp.pack(
            sp.set_type_expr(
 sp.variant("addSigners", [signer3.address]), main.InternalAdminAction
 )
 )
 c1.send_proposal([sp.record(target=target, actions=[action])].run(
 sender=signer1
 )

 s.h2("Signer 1 голосует за предложение")
 c1.vote(0).run(sender=signer1)
        s.h2("Подписант 2 голосует за предложение")
 c1.vote(0).run(sender=signer2)

        s.verify(c1.data.signers.contains(signer3.address))

Контракт MultisigView

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

Python
import smartpy as sp


@sp.module
def main():
 class MultisigView(sp.Contract):
      """Многочлены голосуют за произвольные байты.

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

        Любой байт, набравший необходимое количество голосов, может быть подтвержден с помощью представления.
        """

       def __init__(self, members, required_votes):            Конструктор Args: members (sp.set of sp.address): люди, которые могут подавать заявки и голосовать за lambda.               
 """
                required_votes (sp.nat): количество необходимых голосов
 " " " 
 assert required_votes <= sp.len(
 members
 ), "required_votes must be <= len(members)"
 self.data.proposals = sp.cast(sp.big_map(), sp.big_map[sp.bytes, sp.bool])
 self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.bytes, sp.set[sp.address]]
 )
 self.data.members = sp.cast(members, sp.set[sp.address])
            self.data.required_votes = sp.cast(required_votes, sp.nat)

       @sp.entrypoint
 def submit_proposal(self, bytes):
          """Отправьте новое предложение на голосование.

            Подача предложения не означает голосования за него.

            Args:
 bytes(sp.bytes): байты, предложенные для голосования.
            Возникает:
               `Вы не являетесь членом клуба`
 " " " 
 assert self.data.members.contains(sp.sender), "Вы не являетесь участником"
 self.data.proposals[bytes] = False
 self.data.votes[bytes] = sp.set()

        @sp.entrypoint
 def vote_proposal(self, bytes):
          """Проголосуйте за предложение.

            Голосование против или за не проводится. Если кто-то не согласен с предложением, он
 может не голосовать. Предупреждение: старые предложения, не прошедшие голосование, никогда не становятся
 устаревшими.

            Args:
 id(sp.bytes): байты предложения.
            Возникают:
               `Вы не являетесь членом клуба`, `Предложение не найдено`
 " " " 
 assert self.data.members.contains(sp.sender), "Вы не являетесь участником"
 assert self.data.proposals.contains(bytes), "Предложение не найдено"
 self.data.votes[bytes].add(sp.sender)
            if sp.len(self.data.votes[bytes]) >= self.data.required_votes:
                self.data.proposals[bytes] = True

 @sp.onchain_view()
 def is_voted(self, id):
          """Возвращает логическое число, указывающее, было ли предложение поставлено на голосование.

            Args:
 id (sp.bytes): байты предложения
 Return:
 (sp.bool): True, если предложение было проголосовано, False в противном случае.
            """
           return self.data.proposals.get(id, error="Предложение не найдено")


if "templates" not in __name__:

   @sp.add_test(name="Базовый сценарий MultisigView", is_default=True)
 def basic_scenario():
      """Сценарий с голосованием по контракту multisigView.

        Тесты:
 - Origination
 - Proposal submission
 - Proposal vote
 " " " 
 sc = sp.test_scenario(main)
        sc.h1("Базовый сценарий.")

        member1 = sp.test_account("member1")
        member2 = sp.test_account("member2")
        member3 = sp.test_account("member3")
        members = sp.set([member1.address, member2.address, member3.address])

        sc.h2("Origination")
 c1 = main.MultisigView(members, 2)
 sc += c1

 sc.h2("submit_proposal")
 c1.submit_proposal(sp.bytes("0x42")).run(sender=member1)

 sc.h2("vote_proposal")
 c1.vote_proposal(sp.bytes("0x42")).run(sender=member1)
 c1.vote_proposal(sp.bytes("0x42")).run(sender=member2)

 # Мы можем проверить, что предложение было подтверждено.
        sc.verify(c1.is_voted(sp.bytes("0x42")))

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

Пошаговое руководство по опробованию Multisig-контракта на SmartPy Online

Чтобы опробовать мультисигмальные контракты, которые мы написали на SmartPy, Вы можете выполнить следующие шаги:

  1. Перейдите в среду разработки SmartPy IDE по адресу https://smartpy.io/ide.

  2. Вставьте код контракта в редактор. Вы можете заменить существующий код.

  3. Чтобы выполнить контракт, щелкните на кнопке "Выполнить", расположенной на верхней панели.

  4. После запуска контракта Вы можете просмотреть выполнение сценария на панели "Output" справа. Здесь Вы можете просмотреть детали каждого действия, включая предложения, голосования и одобрения.

  5. Чтобы развернуть свой контракт в сети Tezos, Вам сначала нужно скомпилировать его. Нажмите кнопку "Compile" на верхней панели.

  6. После компиляции Вы можете развернуть контракт в тестовой сети, нажав кнопку "Развернуть контракт Michelson". Вам нужно будет предоставить Секретный ключ для счета Tezos, на котором достаточно средств для оплаты расходов на газ при развертывании.

  7. Как только контракт будет развернут, Вам будет предоставлен адрес контракта в блокчейне. Вы можете использовать этот адрес для взаимодействия с контрактом посредством транзакций.

  8. Для подачи предложений или голосования в контрактах Вы можете использовать точки входа, определенные в коде контракта, такие как submit_proposal или vote_proposal. Их можно вызывать непосредственно из транзакций, которые Вы создаете.

Помните, что хотя SmartPy IDE позволяет Вам протестировать Ваш контракт на смоделированном блокчейне, развертывание контракта в реальной сети Tezos повлечет за собой расходы на газ, которые должны быть оплачены в XTZ, родной криптовалюте сети Tezos.

ข้อจำกัดความรับผิด
* การลงทุนคริปโตมีความเสี่ยงสูง โปรดดำเนินการด้วยความระมัดระวัง หลักสูตรนี้ไม่ได้มีไว้เพื่อเป็นคำแนะนำในการลงทุน
* หลักสูตรนี้สร้างขึ้นโดยผู้เขียนที่ได้เข้าร่วม Gate Learn ความคิดเห็นของผู้เขียนไม่ได้มาจาก Gate Learn