Lição 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): підмножина всіх сторін має схвалити транзакцію. Ця конфігурація часто використовується на практиці, оскільки в ній поєднується безпека з гнучкістю.

Контракти Multisig у Blockchain

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

  • Гаманці: гаманці Multisig використовуються для захисту активів. Вони вимагають від кількох сторін підписувати транзакції, що забезпечує додатковий захист від крадіжок, зовнішніх хакерів і внутрішніх загроз.
  • Децентралізовані автономні організації (DAO): DAO часто використовують контракти з кількома підписами, щоб забезпечити виконання своїх правил управління. Голосування за пропозиції реалізуються як багатопідписні транзакції, а члени DAO діють як підписанти. Пропозиція виконується, якщо вона набере достатню кількість голосів.
  • Операції між ланцюжками: у операціях між ланцюгами багатопідписні контракти можуть діяти як зберігачі активів. Коли активи переміщуються з одного блокчейну в інший, багатопідписний контракт у вихідному ланцюжку може гарантувати, що активи безпечно заблоковані до підтвердження операції в іншому ланцюжку.
    Незважаючи на те, що реалізація багатопідписних контрактів може відрізнятися від одного блокчейну до іншого, основна концепція залишається незмінною – необхідність затвердження транзакції декількома сторонами перед її виконанням. Цей додатковий рівень безпеки робить багатопідписні контракти важливим інструментом у блокчейні та криптопросторі.

Приклад кодування: написання та розгортання контрактів Multisig за допомогою SmartPy

Що стосується наших прикладів коду, ми розглянемо три різні реалізації контракту з мультипідписом:

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

Він досить універсальний і має широкий спектр використання. Для виконання довільних лямбда-функцій потрібні кілька підписів.

Python 
 імпортує smartpy як sp 


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

 клас MultisigLambda(sp.Contract): 
 """Кілька учасників голосують за виконання лямбда-виразок.

        Цей договір можна скласти зі списком адрес і кількістю 
 необхідних голосів. Будь-який учасник може подати скільки завгодно лямбда-виразок і проголосувати 
 за активні пропозиції. Коли лямбда досягає необхідної кількості голосів, викликається її код 
 і виконуються операції виведення. Це дозволяє цьому контракту 
 робити все, що може робити контракт: передавати токени, керувати активами, 
 адмініструвати інший контракт...

        Коли застосовано лямбда-вираз, усі надіслані до цього часу лямбда-вирази дезактивуються.
        Учасники можуть надсилати нові лямбда-вирази.
        """ 

 def __init__(self, members, required_votes): 
 """ Constructor 

 Args: 
 members (sp.set of sp.address): люди, які можуть надсилати та голосувати 
 за лямбда.
                required_votes (sp.nat): необхідна кількість голосів 
 """ 
 assert required_votes <= sp.len( 
 members 
 ), "required_votes має бути <= 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(члени, sp.set[sp.address])
            self.data.required_votes = sp.cast(необхідні_голоси, sp.nat) 

 @sp.entrypoint 
 def submit_lambda(self, lambda_): 
 """Подати новий лямбда на голосування.

            Подання пропозиції не означає голосування за неї.

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

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

            Аргументи: 
 id(sp.nat): ідентифікатор лямбда, за який потрібно проголосувати.
            Піднімає: 
 `Ви не є учасником`, `Лямбда неактивна`, `Лямбда не знайдена` 

 Немає голосів проти або прийнято. Якщо хтось не погоджується з лямбда- 
 він може не голосувати.
            """ 
 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): 
 """Повертає відповідний лямбда.

            Аргументи: 
 id (sp.nat): ідентифікатор лямбда, який потрібно отримати.

            Повернення: 
 пара лямбда та логічне значення, що показує, чи активна лямбда.
            """ 
 повернення (self.data.lambdas[id], id >= self.data.inactiveBefore)


# якщо «шаблони» відсутні в __name__: 


 @sp.module 
 def test(): 
 клас 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 як адміністратора прикладу контракту.

    Тести: 
 - Походження 
 - Подання лямбда 
 - Лямбда голосування 
 """ 
 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("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 
 імпортує smartpy як 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, кворум, підписанти): 
 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, дії=sp.list[sp.bytes])],
                ], 
 ) 
 self.data.quorum = sp.cast(quorum, sp.nat) 
 self.data.signers = sp.cast(підписувачі, 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, offer): 
 """Тільки для підписувача. Винести пропозицію на голосування.

            Аргументи: 
 пропозиція (sp.list of sp.record цільової адреси та дії): Список\ 
 цільових і пов’язаних адміністративних дій.
            """ 
 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): Ідентифікатор пропозиції.
            """ 
 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)

            якщо sp.len(self.data.votes.get(pId, default=sp.set())) >= self.data.quorum:
                self._onApproved(pId)

        @sp.private(with_storage="читання-запис", with_operations=True) 
 def _onApproved(self, pId): 
 """Вбудована функція. Логіка, застосована після схвалення пропозиції."""
            пропозиція = self.data.proposals.get(pId, default=[]) 
 для p_item у пропозиції: 
 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): 
 """Лише самовиклик. Адмініструвати цей контракт.

            Цю точку входу необхідно викликати через систему пропозицій.

            Аргументи: 
 дії (sp.list sp.bytes): список упакованого варіанту \ 
 `InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`).
            """ 
 assert ( 
 sp.sender == sp.self_address() 
 ), "Цю точку входу потрібно викликати через систему пропозицій." 

 для packed_actions у діях: 
 action = sp.unpack(packed_actions, InternalAdminAction).unwrap_some( 
 error="Поганий формат дій" 
 ) 
 з sp.match(action): 
 з sp.case.changeQuorum як кворум: 
 self.data.quorum = кворум 
 із доданим sp.case.addSigners: 
 для доданого підписувача: 
 self.data.signers.add(підписувач)
                    за допомогою sp.case.removeSigners видалено: 
 для видаленої адреси: 
 self.data.signers.remove(адреса)
                # Переконайтеся, що договір ніколи не вимагає більшого кворуму, ніж загальна кількість підписантів.
                assert self.data.quorum <= sp.len( 
 self.data.signers 
 ), "Більше кворуму, ніж підписувачів."


якщо "шаблони" відсутні в __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, 
 підписувачі=sp.set([підписувач1.адреса, 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("Підписаний 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 
 імпортує smartpy як sp 


 @sp.module 
 def main(): 
 class MultisigView(sp.Contract): 
 """Кілька учасників голосують за довільні байти.

        Цей договір можна скласти зі списком адрес і кількістю 
 необхідних голосів. Будь-який учасник може подати скільки завгодно байтів і проголосувати 
 за активні пропозиції.

        Будь-які байти, які досягли необхідної кількості голосів, можна підтвердити за допомогою перегляду.
        """ 

 def __init__(self, members, required_votes): 
 """ Constructor 

 Args: 
 members (sp.set of sp.address): люди, які можуть подавати та голосувати за 
 лямбда.
                required_votes (sp.nat): потрібна кількість голосів 
 """ 
 assert required_votes <= sp.len( 
 members 
 ), "required_votes має бути <= 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(необхідні_голоси, sp.nat) 

 @sp.entrypoint 
 def submit_proposal(self, bytes): 
 """Подати нову пропозицію на голосування.

            Подання пропозиції не означає голосування за неї.

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

        @sp.entrypoint 
 def vote_proposal(self, bytes): 
 """Голосуйте за пропозицію.

            Немає жодного голосу проти чи ухвалення. Якщо хтось не погоджується з пропозицією 
 він може не брати участь у голосуванні. Попередження: старі пропозиції, за які не проголосували, ніколи не 
 .

            Аргументи: 
 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[байти] = True 

 @sp.onchain_view() 
 def is_voted(self, id): 
 """Повертає логічне значення, яке вказує, чи було проголосовано за пропозицію.

            Аргументи: 
 id (sp.bytes): байти пропозиції 
 Повернення: 
 (sp.bool): True, якщо пропозиція була проголосована, False в іншому випадку.
            """ 
 повертає self.data.proposals.get(id, error="Пропозицію не знайдено") 


 , якщо "шаблони" відсутні в __name__: 

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

        Тести: 
 - Створення 
 - Подання пропозиції 
 - Голосування пропозиції 
 """ 
 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("Походження") 
 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. Після запуску контракту ви можете переглянути виконання сценарію на панелі «Вихід» праворуч. Тут ви можете переглянути деталі кожної дії, зокрема пропозиції, голосування та схвалення.

  5. Щоб розгорнути свій контракт у мережі Tezos, вам спочатку потрібно його скомпілювати. Натисніть кнопку «Компілювати» на верхній панелі.

  6. Після компіляції ви можете розгорнути контракт у тестовій мережі, натиснувши «Deploy Michelson Contract». Вам потрібно буде надати секретний ключ для облікового запису Tezos із достатньою кількістю коштів для оплати витрат на газ для розгортання.

  7. Після розгортання контракту вам буде надано адресу контракту в блокчейні. Ви можете використовувати цю адресу для взаємодії з контрактом через транзакції.

  8. Щоб подати пропозиції або проголосувати за контракти, ви можете використовувати точки входу, визначені в коді контракту, наприклад submit_proposal або vote_proposal. Їх можна викликати безпосередньо з транзакцій, які ви створюєте.

Пам’ятайте: незважаючи на те, що SmartPy IDE дозволяє перевірити ваш контракт на симульованому блокчейні, розгортання контракту в реальній мережі Tezos призведе до витрат на газ, які потрібно сплатити в XTZ, рідній криптовалюті мережі Tezos.

Isenção de responsabilidade
* O investimento em criptomoedas envolve grandes riscos. Prossiga com cautela. O curso não se destina a servir de orientação para investimentos.
* O curso foi criado pelo autor que entrou para o Gate Learn. As opiniões compartilhadas pelo autor não representam o Gate Learn.
Catálogo
Lição 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): підмножина всіх сторін має схвалити транзакцію. Ця конфігурація часто використовується на практиці, оскільки в ній поєднується безпека з гнучкістю.

Контракти Multisig у Blockchain

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

  • Гаманці: гаманці Multisig використовуються для захисту активів. Вони вимагають від кількох сторін підписувати транзакції, що забезпечує додатковий захист від крадіжок, зовнішніх хакерів і внутрішніх загроз.
  • Децентралізовані автономні організації (DAO): DAO часто використовують контракти з кількома підписами, щоб забезпечити виконання своїх правил управління. Голосування за пропозиції реалізуються як багатопідписні транзакції, а члени DAO діють як підписанти. Пропозиція виконується, якщо вона набере достатню кількість голосів.
  • Операції між ланцюжками: у операціях між ланцюгами багатопідписні контракти можуть діяти як зберігачі активів. Коли активи переміщуються з одного блокчейну в інший, багатопідписний контракт у вихідному ланцюжку може гарантувати, що активи безпечно заблоковані до підтвердження операції в іншому ланцюжку.
    Незважаючи на те, що реалізація багатопідписних контрактів може відрізнятися від одного блокчейну до іншого, основна концепція залишається незмінною – необхідність затвердження транзакції декількома сторонами перед її виконанням. Цей додатковий рівень безпеки робить багатопідписні контракти важливим інструментом у блокчейні та криптопросторі.

Приклад кодування: написання та розгортання контрактів Multisig за допомогою SmartPy

Що стосується наших прикладів коду, ми розглянемо три різні реалізації контракту з мультипідписом:

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

Він досить універсальний і має широкий спектр використання. Для виконання довільних лямбда-функцій потрібні кілька підписів.

Python 
 імпортує smartpy як sp 


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

 клас MultisigLambda(sp.Contract): 
 """Кілька учасників голосують за виконання лямбда-виразок.

        Цей договір можна скласти зі списком адрес і кількістю 
 необхідних голосів. Будь-який учасник може подати скільки завгодно лямбда-виразок і проголосувати 
 за активні пропозиції. Коли лямбда досягає необхідної кількості голосів, викликається її код 
 і виконуються операції виведення. Це дозволяє цьому контракту 
 робити все, що може робити контракт: передавати токени, керувати активами, 
 адмініструвати інший контракт...

        Коли застосовано лямбда-вираз, усі надіслані до цього часу лямбда-вирази дезактивуються.
        Учасники можуть надсилати нові лямбда-вирази.
        """ 

 def __init__(self, members, required_votes): 
 """ Constructor 

 Args: 
 members (sp.set of sp.address): люди, які можуть надсилати та голосувати 
 за лямбда.
                required_votes (sp.nat): необхідна кількість голосів 
 """ 
 assert required_votes <= sp.len( 
 members 
 ), "required_votes має бути <= 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(члени, sp.set[sp.address])
            self.data.required_votes = sp.cast(необхідні_голоси, sp.nat) 

 @sp.entrypoint 
 def submit_lambda(self, lambda_): 
 """Подати новий лямбда на голосування.

            Подання пропозиції не означає голосування за неї.

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

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

            Аргументи: 
 id(sp.nat): ідентифікатор лямбда, за який потрібно проголосувати.
            Піднімає: 
 `Ви не є учасником`, `Лямбда неактивна`, `Лямбда не знайдена` 

 Немає голосів проти або прийнято. Якщо хтось не погоджується з лямбда- 
 він може не голосувати.
            """ 
 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): 
 """Повертає відповідний лямбда.

            Аргументи: 
 id (sp.nat): ідентифікатор лямбда, який потрібно отримати.

            Повернення: 
 пара лямбда та логічне значення, що показує, чи активна лямбда.
            """ 
 повернення (self.data.lambdas[id], id >= self.data.inactiveBefore)


# якщо «шаблони» відсутні в __name__: 


 @sp.module 
 def test(): 
 клас 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 як адміністратора прикладу контракту.

    Тести: 
 - Походження 
 - Подання лямбда 
 - Лямбда голосування 
 """ 
 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("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 
 імпортує smartpy як 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, кворум, підписанти): 
 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, дії=sp.list[sp.bytes])],
                ], 
 ) 
 self.data.quorum = sp.cast(quorum, sp.nat) 
 self.data.signers = sp.cast(підписувачі, 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, offer): 
 """Тільки для підписувача. Винести пропозицію на голосування.

            Аргументи: 
 пропозиція (sp.list of sp.record цільової адреси та дії): Список\ 
 цільових і пов’язаних адміністративних дій.
            """ 
 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): Ідентифікатор пропозиції.
            """ 
 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)

            якщо sp.len(self.data.votes.get(pId, default=sp.set())) >= self.data.quorum:
                self._onApproved(pId)

        @sp.private(with_storage="читання-запис", with_operations=True) 
 def _onApproved(self, pId): 
 """Вбудована функція. Логіка, застосована після схвалення пропозиції."""
            пропозиція = self.data.proposals.get(pId, default=[]) 
 для p_item у пропозиції: 
 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): 
 """Лише самовиклик. Адмініструвати цей контракт.

            Цю точку входу необхідно викликати через систему пропозицій.

            Аргументи: 
 дії (sp.list sp.bytes): список упакованого варіанту \ 
 `InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`).
            """ 
 assert ( 
 sp.sender == sp.self_address() 
 ), "Цю точку входу потрібно викликати через систему пропозицій." 

 для packed_actions у діях: 
 action = sp.unpack(packed_actions, InternalAdminAction).unwrap_some( 
 error="Поганий формат дій" 
 ) 
 з sp.match(action): 
 з sp.case.changeQuorum як кворум: 
 self.data.quorum = кворум 
 із доданим sp.case.addSigners: 
 для доданого підписувача: 
 self.data.signers.add(підписувач)
                    за допомогою sp.case.removeSigners видалено: 
 для видаленої адреси: 
 self.data.signers.remove(адреса)
                # Переконайтеся, що договір ніколи не вимагає більшого кворуму, ніж загальна кількість підписантів.
                assert self.data.quorum <= sp.len( 
 self.data.signers 
 ), "Більше кворуму, ніж підписувачів."


якщо "шаблони" відсутні в __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, 
 підписувачі=sp.set([підписувач1.адреса, 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("Підписаний 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 
 імпортує smartpy як sp 


 @sp.module 
 def main(): 
 class MultisigView(sp.Contract): 
 """Кілька учасників голосують за довільні байти.

        Цей договір можна скласти зі списком адрес і кількістю 
 необхідних голосів. Будь-який учасник може подати скільки завгодно байтів і проголосувати 
 за активні пропозиції.

        Будь-які байти, які досягли необхідної кількості голосів, можна підтвердити за допомогою перегляду.
        """ 

 def __init__(self, members, required_votes): 
 """ Constructor 

 Args: 
 members (sp.set of sp.address): люди, які можуть подавати та голосувати за 
 лямбда.
                required_votes (sp.nat): потрібна кількість голосів 
 """ 
 assert required_votes <= sp.len( 
 members 
 ), "required_votes має бути <= 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(необхідні_голоси, sp.nat) 

 @sp.entrypoint 
 def submit_proposal(self, bytes): 
 """Подати нову пропозицію на голосування.

            Подання пропозиції не означає голосування за неї.

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

        @sp.entrypoint 
 def vote_proposal(self, bytes): 
 """Голосуйте за пропозицію.

            Немає жодного голосу проти чи ухвалення. Якщо хтось не погоджується з пропозицією 
 він може не брати участь у голосуванні. Попередження: старі пропозиції, за які не проголосували, ніколи не 
 .

            Аргументи: 
 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[байти] = True 

 @sp.onchain_view() 
 def is_voted(self, id): 
 """Повертає логічне значення, яке вказує, чи було проголосовано за пропозицію.

            Аргументи: 
 id (sp.bytes): байти пропозиції 
 Повернення: 
 (sp.bool): True, якщо пропозиція була проголосована, False в іншому випадку.
            """ 
 повертає self.data.proposals.get(id, error="Пропозицію не знайдено") 


 , якщо "шаблони" відсутні в __name__: 

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

        Тести: 
 - Створення 
 - Подання пропозиції 
 - Голосування пропозиції 
 """ 
 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("Походження") 
 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. Після запуску контракту ви можете переглянути виконання сценарію на панелі «Вихід» праворуч. Тут ви можете переглянути деталі кожної дії, зокрема пропозиції, голосування та схвалення.

  5. Щоб розгорнути свій контракт у мережі Tezos, вам спочатку потрібно його скомпілювати. Натисніть кнопку «Компілювати» на верхній панелі.

  6. Після компіляції ви можете розгорнути контракт у тестовій мережі, натиснувши «Deploy Michelson Contract». Вам потрібно буде надати секретний ключ для облікового запису Tezos із достатньою кількістю коштів для оплати витрат на газ для розгортання.

  7. Після розгортання контракту вам буде надано адресу контракту в блокчейні. Ви можете використовувати цю адресу для взаємодії з контрактом через транзакції.

  8. Щоб подати пропозиції або проголосувати за контракти, ви можете використовувати точки входу, визначені в коді контракту, наприклад submit_proposal або vote_proposal. Їх можна викликати безпосередньо з транзакцій, які ви створюєте.

Пам’ятайте: незважаючи на те, що SmartPy IDE дозволяє перевірити ваш контракт на симульованому блокчейні, розгортання контракту в реальній мережі Tezos призведе до витрат на газ, які потрібно сплатити в XTZ, рідній криптовалюті мережі Tezos.

Isenção de responsabilidade
* O investimento em criptomoedas envolve grandes riscos. Prossiga com cautela. O curso não se destina a servir de orientação para investimentos.
* O curso foi criado pelo autor que entrou para o Gate Learn. As opiniões compartilhadas pelo autor não representam o Gate Learn.