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)