Module multisig

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

Functions

def addMultiSig(c, thresholdWeight, thresholdGroupsOK)
Expand source code
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)