Expand source code
# Two Level Multisig - Example for illustrative purposes only.
import smartpy as sp
# MultiSigFactory is a two level multisig factory contract
# - two level because to validate a property, we can use several groups of participants and not only one group,
# - and a factory because it can hold several such two level multisig contracts.
@sp.module
def t():
participant: type = sp.record(hasVoted=sp.bool, weight=sp.int, id=sp.address)
group: type = sp.record(
weight = sp.int,
voters = sp.int,
contractWeight = sp.int,
thresholdWeight = sp.int,
thresholdVoters = sp.int,
participants = sp.list[participant],
ok = sp.bool
)
contract: type = sp.record(
amount = sp.mutez,
name = sp.string,
owner = sp.address,
thresholdGroupsOK = sp.int,
groupsOK = sp.int,
thresholdWeight = sp.int,
weight = sp.int,
groups = sp.list[group],
ok = sp.bool
)
@sp.module
def main():
class MultiSigFactory(sp.Contract):
def __init__(self):
self.data.multisigs = sp.cast(sp.big_map(), sp.big_map[sp.nat, t.contract])
self.data.nbMultisigs = sp.nat(0)
@sp.entrypoint
def build(self, params):
self.data.multisigs[self.data.nbMultisigs] = params.contract
self.data.nbMultisigs += 1
@sp.entrypoint
def sign(self, id, contractId, contractName):
assert id == sp.sender
sp.cast(contractName, sp.string)
contract = self.data.multisigs[contractId]
assert contractName == contract.name
sp.cast(contract.weight, sp.int)
sp.cast(contract.groupsOK, sp.int)
for group in contract.groups:
for participant in group.participants:
if participant.id == id:
assert not participant.hasVoted
participant.hasVoted = True
sp.cast(group.weight, sp.int)
group.weight += participant.weight
group.voters += 1
if not group.ok and group.thresholdVoters <= group.voters and group.thresholdWeight <= group.weight:
group.ok = True
contract.weight += group.contractWeight
contract.groupsOK += 1
if not contract.ok and contract.thresholdGroupsOK <= contract.groupsOK and contract.thresholdWeight <= contract.weight:
contract.ok = True
self.onOK(contract)
@sp.private(with_storage="read-write", with_operations=True)
def onOK(self, contract):
return ()
# MultiSigFactoryWithPayment inherits from MultiSigFactory and adds a
# payment functionality
class MultiSigFactoryWithPayment(MultiSigFactory):
def __init__(self):
MultiSigFactory.__init__(self)
@sp.private(with_storage="read-write", with_operations=True)
def onOK(self, contract):
sp.send(contract.owner, contract.amount)
def addMultiSig(c, thresholdWeight, thresholdGroupsOK):
# tgroup = c.getStorageType().go('multisigs').go('list').go('groups').go('list')
# tparticipant = tgroup.go('participants').go('list')
def group(contractWeight, thresholdWeight, thresholdVoters, participants):
participants = [sp.record(hasVoted = False, weight = weight, id = id) for (id, weight) in participants]
return sp.record(weight = 0,
voters = 0,
contractWeight = contractWeight,
thresholdWeight = thresholdWeight,
thresholdVoters = thresholdVoters,
participants = participants,
ok = False
)
p1 = sp.address("tz1NFevnqBrtcZTZTeKP2YBBjsPs9bih5i3J")
p2 = sp.address("tz1ZRjMiF9K9n3S9AcUrTGUzR2okS7dn9KXS")
p3 = sp.address("tz1NLJRAAwYdggijWz9EFtX5Dgs95BLfD6mP")
g1 = group(5, 5 , 2, [(p1, 2), (p2, 8), (p3, 1)])
g2 = group(7, 5 , 1, [(p1, 7), (p2, 8)])
g3 = group(7, 10, 1, [(p3, 10)])
contract = sp.record(amount = sp.tez(0),
name = "demo",
owner = p1,
thresholdGroupsOK = thresholdGroupsOK,
groupsOK = 0,
thresholdWeight = thresholdWeight,
weight = 0,
groups = sp.list([g1, g2, g3]),
ok = False)
return c.build(contract = contract)
# Tests
@sp.add_test(name = "MultiSig")
def test():
alice = sp.test_account("Alice")
bob = sp.test_account("Rob")
charlie = sp.test_account("Charlie")
scenario = sp.test_scenario([t, main])
c1 = main.MultiSigFactory()
scenario.h1("Multi Sig")
scenario.h2("Contract")
scenario.h3("Simple multisig factories")
scenario += c1
scenario.h2("First: define a simple multisig")
addMultiSig(c1, thresholdWeight = 10, thresholdGroupsOK = 2)
scenario.h2("Message execution")
scenario.h3("A first move")
c1.sign(id = alice.address, contractId = 0, contractName = "demo").run(sender = alice)
c1.sign(id = bob.address, contractId = 0, contractName = "demo").run(sender = bob)
scenario.h2("First: define a simple multi-sig")
addMultiSig(c1, thresholdWeight = 10, thresholdGroupsOK = 3)
scenario.h2("Message execution")
scenario.h3("A first move")
c1.sign(id = alice.address, contractId = 1, contractName = "demo").run(sender = alice)
c1.sign(id = bob.address, contractId = 1, contractName = "demo").run(sender = bob)
scenario.h4("We need a third vote")
c1.sign(id = charlie.address, contractId = 1, contractName = "demo").run(sender = charlie)
scenario.h4("Final state")
scenario.show(c1.data)
scenario.h3("Multisig factories with payments")
c2 = main.MultiSigFactoryWithPayment()
scenario += c2