Контракты Multisig обеспечивают средство создания совместного контроля над активами. Типичные случаи использования включают услуги эскроу, управление корпоративными счетами, совместное подписание финансовых соглашений и многое другое. Такие контракты исключительно выгодны для организаций или групп, где необходимо коллективное принятие решений.
По своей конструкции мультисиговые контракты устойчивы к взлому и предотвращают возникновение единых точек отказа. Даже если ключи одной из сторон скомпрометированы, злоумышленник не сможет выполнять транзакции без одобрения других сторон. Это добавляет дополнительный уровень безопасности.
Мультисиговые контракты можно представить себе как цифровой эквивалент сейфа, для открытия которого требуется несколько ключей. Общее количество ключей (N) и минимальное количество ключей, необходимое для открытия ящика (M), оговариваются при создании контракта.
Контракты Multisig могут иметь множество различных конфигураций в зависимости от значений M и N:
В контексте блокчейна мультисиговые контракты широко используются для повышения безопасности транзакций, поддержки сложных механизмов управления или поддержания гибкого контроля над активами блокчейна. Вот некоторые примеры:
Что касается наших примеров кода, то мы рассмотрим три различные реализации контрактов с несколькими подписями:
Он достаточно универсален и позволяет использовать его в широком диапазоне. Для выполнения произвольных лямбда-функций требуется несколько сигнатур.
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)
Он вводит концепцию голосования за предложения. В этом контракте подписавшие могут голосовать за определенные действия, которые должны быть предприняты, и если кворум достигнут, предложенные действия будут выполнены.
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))
В нем также используется механизм голосования. Этот контракт позволяет членам клуба представлять и голосовать за произвольные байты. Как только предложение набирает необходимое количество голосов, его статус может быть подтвержден с помощью просмотра.
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")))
Каждый контракт предоставляет свой механизм для достижения контроля нескольких подписей, обеспечивая гибкость в зависимости от конкретных потребностей Вашего варианта использования блокчейна.
Чтобы опробовать мультисигмальные контракты, которые мы написали на SmartPy, Вы можете выполнить следующие шаги:
Перейдите в среду разработки SmartPy IDE по адресу https://smartpy.io/ide.
Вставьте код контракта в редактор. Вы можете заменить существующий код.
Чтобы выполнить контракт, щелкните на кнопке "Выполнить", расположенной на верхней панели.
После запуска контракта Вы можете просмотреть выполнение сценария на панели "Output" справа. Здесь Вы можете просмотреть детали каждого действия, включая предложения, голосования и одобрения.
Чтобы развернуть свой контракт в сети Tezos, Вам сначала нужно скомпилировать его. Нажмите кнопку "Compile" на верхней панели.
После компиляции Вы можете развернуть контракт в тестовой сети, нажав кнопку "Развернуть контракт Michelson". Вам нужно будет предоставить Секретный ключ для счета Tezos, на котором достаточно средств для оплаты расходов на газ при развертывании.
Как только контракт будет развернут, Вам будет предоставлен адрес контракта в блокчейне. Вы можете использовать этот адрес для взаимодействия с контрактом посредством транзакций.
Для подачи предложений или голосования в контрактах Вы можете использовать точки входа, определенные в коде контракта, такие как submit_proposal
или vote_proposal
. Их можно вызывать непосредственно из транзакций, которые Вы создаете.
Помните, что хотя SmartPy IDE позволяет Вам протестировать Ваш контракт на смоделированном блокчейне, развертывание контракта в реальной сети Tezos повлечет за собой расходы на газ, которые должны быть оплачены в XTZ, родной криптовалюте сети Tezos.
Контракты Multisig обеспечивают средство создания совместного контроля над активами. Типичные случаи использования включают услуги эскроу, управление корпоративными счетами, совместное подписание финансовых соглашений и многое другое. Такие контракты исключительно выгодны для организаций или групп, где необходимо коллективное принятие решений.
По своей конструкции мультисиговые контракты устойчивы к взлому и предотвращают возникновение единых точек отказа. Даже если ключи одной из сторон скомпрометированы, злоумышленник не сможет выполнять транзакции без одобрения других сторон. Это добавляет дополнительный уровень безопасности.
Мультисиговые контракты можно представить себе как цифровой эквивалент сейфа, для открытия которого требуется несколько ключей. Общее количество ключей (N) и минимальное количество ключей, необходимое для открытия ящика (M), оговариваются при создании контракта.
Контракты Multisig могут иметь множество различных конфигураций в зависимости от значений M и N:
В контексте блокчейна мультисиговые контракты широко используются для повышения безопасности транзакций, поддержки сложных механизмов управления или поддержания гибкого контроля над активами блокчейна. Вот некоторые примеры:
Что касается наших примеров кода, то мы рассмотрим три различные реализации контрактов с несколькими подписями:
Он достаточно универсален и позволяет использовать его в широком диапазоне. Для выполнения произвольных лямбда-функций требуется несколько сигнатур.
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)
Он вводит концепцию голосования за предложения. В этом контракте подписавшие могут голосовать за определенные действия, которые должны быть предприняты, и если кворум достигнут, предложенные действия будут выполнены.
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))
В нем также используется механизм голосования. Этот контракт позволяет членам клуба представлять и голосовать за произвольные байты. Как только предложение набирает необходимое количество голосов, его статус может быть подтвержден с помощью просмотра.
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")))
Каждый контракт предоставляет свой механизм для достижения контроля нескольких подписей, обеспечивая гибкость в зависимости от конкретных потребностей Вашего варианта использования блокчейна.
Чтобы опробовать мультисигмальные контракты, которые мы написали на SmartPy, Вы можете выполнить следующие шаги:
Перейдите в среду разработки SmartPy IDE по адресу https://smartpy.io/ide.
Вставьте код контракта в редактор. Вы можете заменить существующий код.
Чтобы выполнить контракт, щелкните на кнопке "Выполнить", расположенной на верхней панели.
После запуска контракта Вы можете просмотреть выполнение сценария на панели "Output" справа. Здесь Вы можете просмотреть детали каждого действия, включая предложения, голосования и одобрения.
Чтобы развернуть свой контракт в сети Tezos, Вам сначала нужно скомпилировать его. Нажмите кнопку "Compile" на верхней панели.
После компиляции Вы можете развернуть контракт в тестовой сети, нажав кнопку "Развернуть контракт Michelson". Вам нужно будет предоставить Секретный ключ для счета Tezos, на котором достаточно средств для оплаты расходов на газ при развертывании.
Как только контракт будет развернут, Вам будет предоставлен адрес контракта в блокчейне. Вы можете использовать этот адрес для взаимодействия с контрактом посредством транзакций.
Для подачи предложений или голосования в контрактах Вы можете использовать точки входа, определенные в коде контракта, такие как submit_proposal
или vote_proposal
. Их можно вызывать непосредственно из транзакций, которые Вы создаете.
Помните, что хотя SmartPy IDE позволяет Вам протестировать Ваш контракт на смоделированном блокчейне, развертывание контракта в реальной сети Tezos повлечет за собой расходы на газ, которые должны быть оплачены в XTZ, родной криптовалюте сети Tezos.