Multisig-Verträge bieten eine Möglichkeit, eine gemeinsame Kontrolle über Vermögenswerte zu schaffen. Zu den typischen Anwendungsfällen gehören Treuhanddienste, Unternehmenskontoverwaltung, Mitunterzeichnung von Finanzvereinbarungen und mehr. Diese Verträge sind besonders vorteilhaft für Organisationen oder Gruppen, bei denen eine gemeinsame Entscheidungsfindung erforderlich ist.
Multisig-Verträge sind von Natur aus manipulationssicher und verhindern Single Points of Failure. Selbst wenn die Schlüssel einer Partei kompromittiert werden, kann der Angreifer keine Transaktionen ohne Zustimmung der anderen Parteien ausführen. Dies fügt eine zusätzliche Sicherheitsebene hinzu.
Multisig-Verträge können als digitales Äquivalent eines Schließfachs betrachtet werden, für dessen Öffnung mehrere Schlüssel erforderlich sind. Die Gesamtzahl der Schlüssel (N) und die Mindestanzahl der zum Öffnen der Box erforderlichen Schlüssel (M) werden bei Vertragsabschluss vereinbart.
Multisig-Verträge können abhängig von den Werten von M und N viele verschiedene Konfigurationen haben:
Im Kontext der Blockchain werden Multisig-Verträge häufig verwendet, um die Transaktionssicherheit zu verbessern, komplexe Governance-Mechanismen zu unterstützen oder eine flexible Kontrolle über Blockchain-Assets aufrechtzuerhalten. Hier sind einige Beispiele:
Was unsere Codebeispiele betrifft, werden wir uns drei verschiedene Multisignatur-Vertragsimplementierungen ansehen:
Es ist sehr vielseitig und ermöglicht ein breites Einsatzspektrum. Zur Ausführung beliebiger Lambda-Funktionen sind mehrere Signaturen erforderlich.
Python
smartpy als sp importieren
@sp.module
def main():
operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_operations=True)
Klasse MultisigLambda(sp.Contract):
"""Mehrere Mitglieder stimmen für die Ausführung von Lambdas.
Dieser Vertrag kann mit einer Adressenliste und einer Anzahl von
erforderlichen Stimmen zustande kommen. Jedes Mitglied kann so viele Lambdas einreichen, wie es möchte, und
für aktive Vorschläge stimmen. Wenn ein Lambda die erforderlichen Stimmen erreicht, wird sein Code
und die Ausgabeoperationen werden ausgeführt. Dadurch kann dieser Vertrag
alles tun, was ein Vertrag tun kann: Token übertragen, Vermögenswerte verwalten,
einen anderen Vertrag verwalten ...
Wenn ein Lambda angewendet wird, werden alle bisher übermittelten Lambdas inaktiviert.
Die Mitglieder können weiterhin neue Lambdas einreichen.
„““
def __init__(selbst, Mitglieder, erforderliche_Stimmen):
„““Konstruktor
Argumente:
Mitglieder (sp.set of sp.address): Personen, die
für Lambda einreichen und abstimmen können.
erforderliche_Stimmen (sp.nat): Anzahl der erforderlichen Stimmen
„““
gelten erforderliche_Stimmen <= sp.len(
Mitglieder
), „erforderliche_Stimmen müssen <= len(Mitglieder) sein“
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 subscribe_lambda(self, lambda_):
"""Einen neuen Lambda zur Abstimmung einreichen.
Das Einreichen eines Vorschlags bedeutet nicht, dass Sie dafür stimmen.
Argumente:
lambda_(sp.lambda mit Operationen): Lambda zur Abstimmung vorgeschlagen.
Löst aus:
„Sie sind kein Mitglied“
„““
Assert self.data.members.contains(sp.sender), „Sie sind kein Mitglied“
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):
"""Vote for a lambda.
Argumente:
id(sp.nat): ID des Lambda, für das man stimmen soll.
Erhöht:
„Sie sind kein Mitglied“, „Das Lambda ist inaktiv“, „Lambda nicht gefunden“
Es gibt keine Gegenstimme oder Annahme. Wenn jemand mit einem Lambda
nicht einverstanden ist, kann er die Abstimmung vermeiden.
„““
behaupten self.data.members.contains(sp.sender), „Sie sind kein Mitglied“
Assert id >= self.data.inactiveBefore, „Das Lambda ist inaktiv“
affirm self.data.lambdas.contains(id), „Lambda nicht gefunden“
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):
"""Gibt das entsprechende Lambda zurück.
Argumente:
id (sp.nat): ID des abzurufenden Lambda.
Rückgabe:
Paar des Lambda und ein boolescher Wert, der anzeigt, ob das Lambda aktiv ist.
„““
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 Basisszenario", is_default=True )
def basic_scenario():
"""Verwenden Sie multisigLambda als Administrator eines Beispielvertrags.
Tests:
– Entstehung
– Lambda-Einreichung
– Lambda-Abstimmung
„““
sc = sp.test_scenario([main, test])
sc.h1("Grundszenario.")
member1 = sp.test_account("member1")
member2 = sp.test_account("member2")
member3 = sp.test_account("member3")
Mitglieder = sp.set([Mitglied1.Adresse, 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: subscribe_lambda")
def set_42(params):
administrated = sp.contract(sp.TInt, c2.address, enterpoint="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)
# Wir können überprüfen, ob der verwaltete Vertrag die Überweisung erhalten hat.
sc.verify(c2.data.value == 42)
Es führt das Konzept der Abstimmung über Vorschläge ein. In diesem Vertrag können die Unterzeichner für bestimmte Maßnahmen stimmen, und wenn ein Quorum erreicht ist, werden die vorgeschlagenen Maßnahmen ausgeführt.
Python
import smartpy as sp
@sp.module
def main():
# Spezifikation des internen Verwaltungsaktionstyps
InternalAdminAction: type = sp.variant(
addSigners=sp.list[sp.address],
changeQuorum=sp.nat,
removeSigners=sp.list[sp.address],
)
Klasse MultisigAction(sp.Contract):
"""Ein Vertrag, der von mehreren Unterzeichnern zur Verwaltung anderer
Verträge verwendet werden kann. Die verwalteten Verträge implementieren eine Schnittstelle, die es
ermöglicht, den Verwaltungsvorgang auch für nicht erfahrene Benutzer zu erläutern.
Unterzeichner stimmen für Vorschläge. Ein Vorschlag ist eine Liste eines Ziels mit einer Liste von
Aktion. Eine Aktion ist ein einfaches Byte, soll aber einen Paketwert von
pro Variante haben. Dieses einfache Muster ermöglicht es, eine UX-Schnittstelle
zu erstellen, die den Inhalt eines Vorschlags anzeigt, oder einen solchen zu erstellen.
„““
def __init__(selbst, Quorum, Unterzeichner):
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):
"""Nur für Unterzeichner. Reichen Sie einen Vorschlag zur Abstimmung ein.
Argumente:
Vorschlag (SP.Liste des SP.Datensatzes der Zieladresse und Aktion): Liste\
des Ziels und zugehöriger Verwaltungsaktionen.
„““
behaupten self.data.signers.contains(sp.sender), „Nur Unterzeichner können Vorschläge machen“
self.data.proposals[self.data.nextId] = Vorschlag
self.data.votes[self.data.nextId] = sp.set()
self.data.nextId += 1
@sp.entrypoint
def vote(self, pId):
"""Vote für einen oder mehrere Vorschläge
Args:
pId (sp.nat): ID des Vorschlags.
„““
behaupten self.data.signers.contains(sp.sender), „Nur Unterzeichner können abstimmen“
affirm self.data.votes.contains(pId), „Vorschlag unbekannt“
Assert pId >= self.data.inactiveBefore, „Der Vorschlag ist inaktiv“
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):
"""Inline-Funktion. Angewendete Logik, wenn ein Vorschlag genehmigt wurde.“
Vorschlag = self.data.proposals.get(pId, default=[])
für p_item im Vorschlag:
Contract = sp.contract(sp.list[sp.bytes], p_item.target)
sp.transfer(
p_item.actions,
sp.tez(0),
(error="InvalidTarget"),
)
# Alle bereits eingereichten Vorschläge deaktivieren.
self.data.inactiveBefore = self.data.nextId
@sp.entrypoint
def administrate(self, actions):
"""Nur Selbstaufruf. Verwalten Sie diesen Vertrag.
Dieser Einstiegspunkt muss über das Vorschlagssystem aufgerufen werden.
Argumente:
Aktionen (sp.list of sp.bytes): Liste der gepackten Variante von \
„InternalAdminAction“ („addSigners“, „changeQuorum“, „removeSigners“).
„““
Assert (
sp.sender == sp.self_address()
), „Dieser Einstiegspunkt muss über das Vorschlagssystem aufgerufen werden.“
für gepackte_Aktionen in Aktionen:
Aktion = sp.unpack(packed_actions, InternalAdminAction).unwrap_some(
error="Ungültiges Aktionsformat"
)
mit sp.match(action):
mit sp.case.changeQuorum als Quorum:
self.data.quorum = Quorum
mit sp.case.addSigners als hinzugefügt:
für Unterzeichner hinzugefügt:
self.data.signers.add(signer)
mit sp.case.removeSigners wie entfernt:
für Adresse in entfernt:
self.data.signers.remove(address)
# Stellen Sie sicher, dass der Vertrag niemals mehr Quorum erfordert als die Gesamtzahl der Unterzeichner.
behaupten self.data.quorum <= sp.len(
self.data.signers
), „Mehr Quorum als Unterzeichner.“
wenn „Vorlagen“ nicht in __name__:
@sp.add_test(name="Grundszenario", 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("Grundszenario")
s.h2("Origination")
c1 = main.MultisigAction(
quorum=2,
signers=sp.set([signer1.address, signer2.address]),
)
s += c1
s.h2("Vorschlag zum Hinzufügen eines neuen Unterzeichners")
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("Unterzeichner 1 stimmt für den Vorschlag")
c1.vote(0).run(sender=signer1)
s.h2("Unterzeichner 2 stimmt für den Vorschlag")
c1.vote(0).run(sender=signer2)
s.verify(c1.data.signers.contains(signer3.address))
Es nutzt auch einen Abstimmungsmechanismus. Dieser Vertrag ermöglicht es Mitgliedern, beliebige Bytes einzureichen und dafür zu stimmen. Sobald ein Vorschlag die erforderliche Anzahl an Stimmen erreicht, kann sein Status über eine Ansicht bestätigt werden.
Python
import smartpy as sp
@sp.module
def main():
class MultisigView(sp.Contract):
"""Mehrere Mitglieder stimmen für beliebige Bytes.
Dieser Vertrag kann mit einer Adressenliste und einer Anzahl von
erforderlichen Stimmen zustande kommen. Jedes Mitglied kann so viele Bytes einreichen, wie es möchte, und
für aktive Vorschläge stimmen.
Alle Bytes, die die erforderlichen Stimmen erreicht haben, können über eine Ansicht bestätigt werden.
„““
def __init__(selbst, Mitglieder, erforderliche_Stimmen):
„““Konstruktor
Argumente:
Mitglieder (sp.set of sp.address): Personen, die
Lambda einreichen und dafür stimmen können.
erforderliche_Stimmen (sp.nat): Anzahl der erforderlichen Stimmen
„““
gelten erforderliche_Stimmen <= sp.len(
Mitglieder
), „erforderliche_Stimmen müssen <= len(Mitglieder) sein“
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 subscribe_proposal(self, bytes):
"""Einen neuen Vorschlag zur Abstimmung einreichen.
Das Einreichen eines Vorschlags bedeutet nicht, dass Sie dafür stimmen.
Argumente:
Bytes(sp.bytes): Bytes zur Abstimmung vorgeschlagen.
Löst aus:
„Sie sind kein Mitglied“
„““
Assert self.data.members.contains(sp.sender), „Sie sind kein Mitglied“
self.data.proposals[bytes] = Falsch
self.data.votes[Bytes] = sp.set()
@sp.entrypoint
def vote_proposal(self, bytes):
"""Stimmen Sie für einen Vorschlag.
Es gibt keine Gegenstimme oder Annahme. Wenn jemand mit einem Vorschlag nicht einverstanden ist
kann er die Abstimmung vermeiden. Achtung: Alte, nicht abgestimmte Vorschläge werden
obsolet.
Argumente:
id(sp.bytes): Bytes des Vorschlags.
Löst aus:
„Sie sind kein Mitglied“, „Vorschlag nicht gefunden“,
„““
„asset self.data.members.contains(sp.sender),“ „Sie sind kein Mitglied“
Assertion self.data.proposals.contains(bytes), „Vorschlag nicht gefunden“
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):
"""Gibt einen booleschen Wert zurück, der angibt, ob über den Vorschlag abgestimmt wurde.
Argumente:
ID (sp.bytes): Bytes des Vorschlags
Rückgabe:
(sp.bool): True, wenn über den Vorschlag abgestimmt wurde, andernfalls False.
„““
return self.data.proposals.get(id, error="Vorschlag nicht gefunden")
wenn "Vorlagen" nicht in __name__:
@sp.add_test(name="MultisigView Basisszenario", is_default=True)
def basic_scenario():
"""Ein Szenario mit eine Abstimmung über den MultisigView-Vertrag.
Tests:
– Entstehung
– Vorschlagseinreichung
– Vorschlagabstimmung
„““
sc = sp.test_scenario(main)
sc.h1("Grundszenario.")
member1 = sp.test_account("member1")
member2 = sp.test_account("member2")
member3 = sp.test_account("member3")
Mitglieder = sp.set([Mitglied1.Adresse, 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)
# Wir können überprüfen, ob der Vorschlag validiert wurde.
sc.verify(c1.is_voted(sp.bytes("0x42")))
Jeder Vertrag bietet einen anderen Mechanismus zur Erzielung einer Multi-Signatur-Kontrolle und bietet so Flexibilität je nach den spezifischen Anforderungen Ihres Blockchain-Anwendungsfalls.
Um die von uns in SmartPy geschriebenen Multisig-Verträge auszuprobieren, können Sie die folgenden Schritte ausführen:
Gehen Sie zur SmartPy-IDE unter https://smartpy.io/ide.
Fügen Sie den Vertragscode in den Editor ein. Sie können den vorhandenen Code ersetzen.
Um den Vertrag auszuführen, klicken Sie auf die Schaltfläche „Ausführen“ im oberen Bereich.
Nachdem Sie den Vertrag ausgeführt haben, können Sie die Szenarioausführung im Bereich „Ausgabe“ auf der rechten Seite anzeigen. Hier können Sie Details zu jeder Aktion einsehen, einschließlich Vorschlägen, Abstimmungen und Genehmigungen.
Um Ihren Vertrag im Tezos-Netzwerk bereitzustellen, müssen Sie ihn zunächst kompilieren. Klicken Sie im oberen Bereich auf die Schaltfläche „Kompilieren“.
Nach dem Kompilieren können Sie den Vertrag im Testnetz bereitstellen, indem Sie auf „Michelson-Vertrag bereitstellen“ klicken. Sie müssen einen Geheimschlüssel für ein Tezos-Konto bereitstellen, das über ausreichend Guthaben verfügt, um die Gaskosten für die Bereitstellung zu bezahlen.
Sobald der Vertrag bereitgestellt wird, erhalten Sie die Adresse des Vertrags in der Blockchain. Über diese Adresse können Sie über Transaktionen mit dem Vertrag interagieren.
Um Vorschläge einzureichen oder über die Verträge abzustimmen, können Sie die im Vertragscode definierten Einstiegspunkte verwenden, z. B. submit_proposal
“ oder vote_proposal
. Diese können direkt aus den von Ihnen erstellten Transaktionen aufgerufen werden.
Denken Sie daran, dass Sie mit der SmartPy-IDE Ihren Vertrag zwar auf einer simulierten Blockchain testen können, für die Bereitstellung des Vertrags im tatsächlichen Tezos-Netzwerk jedoch Gaskosten anfallen, die in XTZ, der nativen Kryptowährung des Tezos-Netzwerks, bezahlt werden müssen.
Multisig-Verträge bieten eine Möglichkeit, eine gemeinsame Kontrolle über Vermögenswerte zu schaffen. Zu den typischen Anwendungsfällen gehören Treuhanddienste, Unternehmenskontoverwaltung, Mitunterzeichnung von Finanzvereinbarungen und mehr. Diese Verträge sind besonders vorteilhaft für Organisationen oder Gruppen, bei denen eine gemeinsame Entscheidungsfindung erforderlich ist.
Multisig-Verträge sind von Natur aus manipulationssicher und verhindern Single Points of Failure. Selbst wenn die Schlüssel einer Partei kompromittiert werden, kann der Angreifer keine Transaktionen ohne Zustimmung der anderen Parteien ausführen. Dies fügt eine zusätzliche Sicherheitsebene hinzu.
Multisig-Verträge können als digitales Äquivalent eines Schließfachs betrachtet werden, für dessen Öffnung mehrere Schlüssel erforderlich sind. Die Gesamtzahl der Schlüssel (N) und die Mindestanzahl der zum Öffnen der Box erforderlichen Schlüssel (M) werden bei Vertragsabschluss vereinbart.
Multisig-Verträge können abhängig von den Werten von M und N viele verschiedene Konfigurationen haben:
Im Kontext der Blockchain werden Multisig-Verträge häufig verwendet, um die Transaktionssicherheit zu verbessern, komplexe Governance-Mechanismen zu unterstützen oder eine flexible Kontrolle über Blockchain-Assets aufrechtzuerhalten. Hier sind einige Beispiele:
Was unsere Codebeispiele betrifft, werden wir uns drei verschiedene Multisignatur-Vertragsimplementierungen ansehen:
Es ist sehr vielseitig und ermöglicht ein breites Einsatzspektrum. Zur Ausführung beliebiger Lambda-Funktionen sind mehrere Signaturen erforderlich.
Python
smartpy als sp importieren
@sp.module
def main():
operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_operations=True)
Klasse MultisigLambda(sp.Contract):
"""Mehrere Mitglieder stimmen für die Ausführung von Lambdas.
Dieser Vertrag kann mit einer Adressenliste und einer Anzahl von
erforderlichen Stimmen zustande kommen. Jedes Mitglied kann so viele Lambdas einreichen, wie es möchte, und
für aktive Vorschläge stimmen. Wenn ein Lambda die erforderlichen Stimmen erreicht, wird sein Code
und die Ausgabeoperationen werden ausgeführt. Dadurch kann dieser Vertrag
alles tun, was ein Vertrag tun kann: Token übertragen, Vermögenswerte verwalten,
einen anderen Vertrag verwalten ...
Wenn ein Lambda angewendet wird, werden alle bisher übermittelten Lambdas inaktiviert.
Die Mitglieder können weiterhin neue Lambdas einreichen.
„““
def __init__(selbst, Mitglieder, erforderliche_Stimmen):
„““Konstruktor
Argumente:
Mitglieder (sp.set of sp.address): Personen, die
für Lambda einreichen und abstimmen können.
erforderliche_Stimmen (sp.nat): Anzahl der erforderlichen Stimmen
„““
gelten erforderliche_Stimmen <= sp.len(
Mitglieder
), „erforderliche_Stimmen müssen <= len(Mitglieder) sein“
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 subscribe_lambda(self, lambda_):
"""Einen neuen Lambda zur Abstimmung einreichen.
Das Einreichen eines Vorschlags bedeutet nicht, dass Sie dafür stimmen.
Argumente:
lambda_(sp.lambda mit Operationen): Lambda zur Abstimmung vorgeschlagen.
Löst aus:
„Sie sind kein Mitglied“
„““
Assert self.data.members.contains(sp.sender), „Sie sind kein Mitglied“
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):
"""Vote for a lambda.
Argumente:
id(sp.nat): ID des Lambda, für das man stimmen soll.
Erhöht:
„Sie sind kein Mitglied“, „Das Lambda ist inaktiv“, „Lambda nicht gefunden“
Es gibt keine Gegenstimme oder Annahme. Wenn jemand mit einem Lambda
nicht einverstanden ist, kann er die Abstimmung vermeiden.
„““
behaupten self.data.members.contains(sp.sender), „Sie sind kein Mitglied“
Assert id >= self.data.inactiveBefore, „Das Lambda ist inaktiv“
affirm self.data.lambdas.contains(id), „Lambda nicht gefunden“
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):
"""Gibt das entsprechende Lambda zurück.
Argumente:
id (sp.nat): ID des abzurufenden Lambda.
Rückgabe:
Paar des Lambda und ein boolescher Wert, der anzeigt, ob das Lambda aktiv ist.
„““
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 Basisszenario", is_default=True )
def basic_scenario():
"""Verwenden Sie multisigLambda als Administrator eines Beispielvertrags.
Tests:
– Entstehung
– Lambda-Einreichung
– Lambda-Abstimmung
„““
sc = sp.test_scenario([main, test])
sc.h1("Grundszenario.")
member1 = sp.test_account("member1")
member2 = sp.test_account("member2")
member3 = sp.test_account("member3")
Mitglieder = sp.set([Mitglied1.Adresse, 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: subscribe_lambda")
def set_42(params):
administrated = sp.contract(sp.TInt, c2.address, enterpoint="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)
# Wir können überprüfen, ob der verwaltete Vertrag die Überweisung erhalten hat.
sc.verify(c2.data.value == 42)
Es führt das Konzept der Abstimmung über Vorschläge ein. In diesem Vertrag können die Unterzeichner für bestimmte Maßnahmen stimmen, und wenn ein Quorum erreicht ist, werden die vorgeschlagenen Maßnahmen ausgeführt.
Python
import smartpy as sp
@sp.module
def main():
# Spezifikation des internen Verwaltungsaktionstyps
InternalAdminAction: type = sp.variant(
addSigners=sp.list[sp.address],
changeQuorum=sp.nat,
removeSigners=sp.list[sp.address],
)
Klasse MultisigAction(sp.Contract):
"""Ein Vertrag, der von mehreren Unterzeichnern zur Verwaltung anderer
Verträge verwendet werden kann. Die verwalteten Verträge implementieren eine Schnittstelle, die es
ermöglicht, den Verwaltungsvorgang auch für nicht erfahrene Benutzer zu erläutern.
Unterzeichner stimmen für Vorschläge. Ein Vorschlag ist eine Liste eines Ziels mit einer Liste von
Aktion. Eine Aktion ist ein einfaches Byte, soll aber einen Paketwert von
pro Variante haben. Dieses einfache Muster ermöglicht es, eine UX-Schnittstelle
zu erstellen, die den Inhalt eines Vorschlags anzeigt, oder einen solchen zu erstellen.
„““
def __init__(selbst, Quorum, Unterzeichner):
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):
"""Nur für Unterzeichner. Reichen Sie einen Vorschlag zur Abstimmung ein.
Argumente:
Vorschlag (SP.Liste des SP.Datensatzes der Zieladresse und Aktion): Liste\
des Ziels und zugehöriger Verwaltungsaktionen.
„““
behaupten self.data.signers.contains(sp.sender), „Nur Unterzeichner können Vorschläge machen“
self.data.proposals[self.data.nextId] = Vorschlag
self.data.votes[self.data.nextId] = sp.set()
self.data.nextId += 1
@sp.entrypoint
def vote(self, pId):
"""Vote für einen oder mehrere Vorschläge
Args:
pId (sp.nat): ID des Vorschlags.
„““
behaupten self.data.signers.contains(sp.sender), „Nur Unterzeichner können abstimmen“
affirm self.data.votes.contains(pId), „Vorschlag unbekannt“
Assert pId >= self.data.inactiveBefore, „Der Vorschlag ist inaktiv“
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):
"""Inline-Funktion. Angewendete Logik, wenn ein Vorschlag genehmigt wurde.“
Vorschlag = self.data.proposals.get(pId, default=[])
für p_item im Vorschlag:
Contract = sp.contract(sp.list[sp.bytes], p_item.target)
sp.transfer(
p_item.actions,
sp.tez(0),
(error="InvalidTarget"),
)
# Alle bereits eingereichten Vorschläge deaktivieren.
self.data.inactiveBefore = self.data.nextId
@sp.entrypoint
def administrate(self, actions):
"""Nur Selbstaufruf. Verwalten Sie diesen Vertrag.
Dieser Einstiegspunkt muss über das Vorschlagssystem aufgerufen werden.
Argumente:
Aktionen (sp.list of sp.bytes): Liste der gepackten Variante von \
„InternalAdminAction“ („addSigners“, „changeQuorum“, „removeSigners“).
„““
Assert (
sp.sender == sp.self_address()
), „Dieser Einstiegspunkt muss über das Vorschlagssystem aufgerufen werden.“
für gepackte_Aktionen in Aktionen:
Aktion = sp.unpack(packed_actions, InternalAdminAction).unwrap_some(
error="Ungültiges Aktionsformat"
)
mit sp.match(action):
mit sp.case.changeQuorum als Quorum:
self.data.quorum = Quorum
mit sp.case.addSigners als hinzugefügt:
für Unterzeichner hinzugefügt:
self.data.signers.add(signer)
mit sp.case.removeSigners wie entfernt:
für Adresse in entfernt:
self.data.signers.remove(address)
# Stellen Sie sicher, dass der Vertrag niemals mehr Quorum erfordert als die Gesamtzahl der Unterzeichner.
behaupten self.data.quorum <= sp.len(
self.data.signers
), „Mehr Quorum als Unterzeichner.“
wenn „Vorlagen“ nicht in __name__:
@sp.add_test(name="Grundszenario", 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("Grundszenario")
s.h2("Origination")
c1 = main.MultisigAction(
quorum=2,
signers=sp.set([signer1.address, signer2.address]),
)
s += c1
s.h2("Vorschlag zum Hinzufügen eines neuen Unterzeichners")
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("Unterzeichner 1 stimmt für den Vorschlag")
c1.vote(0).run(sender=signer1)
s.h2("Unterzeichner 2 stimmt für den Vorschlag")
c1.vote(0).run(sender=signer2)
s.verify(c1.data.signers.contains(signer3.address))
Es nutzt auch einen Abstimmungsmechanismus. Dieser Vertrag ermöglicht es Mitgliedern, beliebige Bytes einzureichen und dafür zu stimmen. Sobald ein Vorschlag die erforderliche Anzahl an Stimmen erreicht, kann sein Status über eine Ansicht bestätigt werden.
Python
import smartpy as sp
@sp.module
def main():
class MultisigView(sp.Contract):
"""Mehrere Mitglieder stimmen für beliebige Bytes.
Dieser Vertrag kann mit einer Adressenliste und einer Anzahl von
erforderlichen Stimmen zustande kommen. Jedes Mitglied kann so viele Bytes einreichen, wie es möchte, und
für aktive Vorschläge stimmen.
Alle Bytes, die die erforderlichen Stimmen erreicht haben, können über eine Ansicht bestätigt werden.
„““
def __init__(selbst, Mitglieder, erforderliche_Stimmen):
„““Konstruktor
Argumente:
Mitglieder (sp.set of sp.address): Personen, die
Lambda einreichen und dafür stimmen können.
erforderliche_Stimmen (sp.nat): Anzahl der erforderlichen Stimmen
„““
gelten erforderliche_Stimmen <= sp.len(
Mitglieder
), „erforderliche_Stimmen müssen <= len(Mitglieder) sein“
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 subscribe_proposal(self, bytes):
"""Einen neuen Vorschlag zur Abstimmung einreichen.
Das Einreichen eines Vorschlags bedeutet nicht, dass Sie dafür stimmen.
Argumente:
Bytes(sp.bytes): Bytes zur Abstimmung vorgeschlagen.
Löst aus:
„Sie sind kein Mitglied“
„““
Assert self.data.members.contains(sp.sender), „Sie sind kein Mitglied“
self.data.proposals[bytes] = Falsch
self.data.votes[Bytes] = sp.set()
@sp.entrypoint
def vote_proposal(self, bytes):
"""Stimmen Sie für einen Vorschlag.
Es gibt keine Gegenstimme oder Annahme. Wenn jemand mit einem Vorschlag nicht einverstanden ist
kann er die Abstimmung vermeiden. Achtung: Alte, nicht abgestimmte Vorschläge werden
obsolet.
Argumente:
id(sp.bytes): Bytes des Vorschlags.
Löst aus:
„Sie sind kein Mitglied“, „Vorschlag nicht gefunden“,
„““
„asset self.data.members.contains(sp.sender),“ „Sie sind kein Mitglied“
Assertion self.data.proposals.contains(bytes), „Vorschlag nicht gefunden“
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):
"""Gibt einen booleschen Wert zurück, der angibt, ob über den Vorschlag abgestimmt wurde.
Argumente:
ID (sp.bytes): Bytes des Vorschlags
Rückgabe:
(sp.bool): True, wenn über den Vorschlag abgestimmt wurde, andernfalls False.
„““
return self.data.proposals.get(id, error="Vorschlag nicht gefunden")
wenn "Vorlagen" nicht in __name__:
@sp.add_test(name="MultisigView Basisszenario", is_default=True)
def basic_scenario():
"""Ein Szenario mit eine Abstimmung über den MultisigView-Vertrag.
Tests:
– Entstehung
– Vorschlagseinreichung
– Vorschlagabstimmung
„““
sc = sp.test_scenario(main)
sc.h1("Grundszenario.")
member1 = sp.test_account("member1")
member2 = sp.test_account("member2")
member3 = sp.test_account("member3")
Mitglieder = sp.set([Mitglied1.Adresse, 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)
# Wir können überprüfen, ob der Vorschlag validiert wurde.
sc.verify(c1.is_voted(sp.bytes("0x42")))
Jeder Vertrag bietet einen anderen Mechanismus zur Erzielung einer Multi-Signatur-Kontrolle und bietet so Flexibilität je nach den spezifischen Anforderungen Ihres Blockchain-Anwendungsfalls.
Um die von uns in SmartPy geschriebenen Multisig-Verträge auszuprobieren, können Sie die folgenden Schritte ausführen:
Gehen Sie zur SmartPy-IDE unter https://smartpy.io/ide.
Fügen Sie den Vertragscode in den Editor ein. Sie können den vorhandenen Code ersetzen.
Um den Vertrag auszuführen, klicken Sie auf die Schaltfläche „Ausführen“ im oberen Bereich.
Nachdem Sie den Vertrag ausgeführt haben, können Sie die Szenarioausführung im Bereich „Ausgabe“ auf der rechten Seite anzeigen. Hier können Sie Details zu jeder Aktion einsehen, einschließlich Vorschlägen, Abstimmungen und Genehmigungen.
Um Ihren Vertrag im Tezos-Netzwerk bereitzustellen, müssen Sie ihn zunächst kompilieren. Klicken Sie im oberen Bereich auf die Schaltfläche „Kompilieren“.
Nach dem Kompilieren können Sie den Vertrag im Testnetz bereitstellen, indem Sie auf „Michelson-Vertrag bereitstellen“ klicken. Sie müssen einen Geheimschlüssel für ein Tezos-Konto bereitstellen, das über ausreichend Guthaben verfügt, um die Gaskosten für die Bereitstellung zu bezahlen.
Sobald der Vertrag bereitgestellt wird, erhalten Sie die Adresse des Vertrags in der Blockchain. Über diese Adresse können Sie über Transaktionen mit dem Vertrag interagieren.
Um Vorschläge einzureichen oder über die Verträge abzustimmen, können Sie die im Vertragscode definierten Einstiegspunkte verwenden, z. B. submit_proposal
“ oder vote_proposal
. Diese können direkt aus den von Ihnen erstellten Transaktionen aufgerufen werden.
Denken Sie daran, dass Sie mit der SmartPy-IDE Ihren Vertrag zwar auf einer simulierten Blockchain testen können, für die Bereitstellung des Vertrags im tatsächlichen Tezos-Netzwerk jedoch Gaskosten anfallen, die in XTZ, der nativen Kryptowährung des Tezos-Netzwerks, bezahlt werden müssen.