templates.multisig

  1# Two Level Multisig - Example for illustrative purposes only.
  2
  3import smartpy as sp
  4
  5# MultiSigFactory is a two level multisig factory contract
  6# - two level because to validate a property, we can use several groups of participants and not only one group,
  7# - and a factory because it can hold several such two level multisig contracts.
  8
  9
 10@sp.module
 11def t():
 12    participant: type = sp.record(hasVoted=sp.bool, weight=sp.int, id=sp.address)
 13
 14    group: type = sp.record(
 15        weight=sp.int,
 16        voters=sp.int,
 17        contractWeight=sp.int,
 18        thresholdWeight=sp.int,
 19        thresholdVoters=sp.int,
 20        participants=sp.list[participant],
 21        ok=sp.bool,
 22    )
 23
 24    contract: type = sp.record(
 25        amount=sp.mutez,
 26        name=sp.string,
 27        owner=sp.address,
 28        thresholdGroupsOK=sp.int,
 29        groupsOK=sp.int,
 30        thresholdWeight=sp.int,
 31        weight=sp.int,
 32        groups=sp.list[group],
 33        ok=sp.bool,
 34    )
 35
 36
 37@sp.module
 38def main():
 39    class MultiSigFactory(sp.Contract):
 40        def __init__(self):
 41            self.data.multisigs = sp.cast(sp.big_map(), sp.big_map[sp.nat, t.contract])
 42            self.data.nbMultisigs = sp.nat(0)
 43
 44        @sp.entrypoint
 45        def build(self, params):
 46            self.data.multisigs[self.data.nbMultisigs] = params.contract
 47            self.data.nbMultisigs += 1
 48
 49        @sp.entrypoint
 50        def sign(self, id, contractId, contractName):
 51            assert id == sp.sender
 52            sp.cast(contractName, sp.string)
 53            contract = self.data.multisigs[contractId]
 54            assert contractName == contract.name
 55            sp.cast(contract.weight, sp.int)
 56            sp.cast(contract.groupsOK, sp.int)
 57            for group in contract.groups:
 58                for participant in group.participants:
 59                    if participant.id == id:
 60                        assert not participant.hasVoted
 61                        participant.hasVoted = True
 62                        sp.cast(group.weight, sp.int)
 63                        group.weight += participant.weight
 64                        group.voters += 1
 65                        if (
 66                            not group.ok
 67                            and group.thresholdVoters <= group.voters
 68                            and group.thresholdWeight <= group.weight
 69                        ):
 70                            group.ok = True
 71                            contract.weight += group.contractWeight
 72                            contract.groupsOK += 1
 73                            if (
 74                                not contract.ok
 75                                and contract.thresholdGroupsOK <= contract.groupsOK
 76                                and contract.thresholdWeight <= contract.weight
 77                            ):
 78                                contract.ok = True
 79                                self.onOK(contract)
 80
 81        @sp.private(with_storage="read-write", with_operations=True)
 82        def onOK(self, contract):
 83            return ()
 84
 85    # MultiSigFactoryWithPayment inherits from MultiSigFactory and adds a
 86    # payment functionality
 87
 88    class MultiSigFactoryWithPayment(MultiSigFactory):
 89        def __init__(self):
 90            MultiSigFactory.__init__(self)
 91
 92        @sp.private(with_storage="read-write", with_operations=True)
 93        def onOK(self, contract):
 94            sp.send(contract.owner, contract.amount)
 95
 96
 97def addMultiSig(c, thresholdWeight, thresholdGroupsOK):
 98    # tgroup = c.getStorageType().go('multisigs').go('list').go('groups').go('list')
 99    # tparticipant = tgroup.go('participants').go('list')
100    def group(contractWeight, thresholdWeight, thresholdVoters, participants):
101        participants = [
102            sp.record(hasVoted=False, weight=weight, id=id)
103            for (id, weight) in participants
104        ]
105        return sp.record(
106            weight=0,
107            voters=0,
108            contractWeight=contractWeight,
109            thresholdWeight=thresholdWeight,
110            thresholdVoters=thresholdVoters,
111            participants=participants,
112            ok=False,
113        )
114
115    p1 = sp.address("tz1NFevnqBrtcZTZTeKP2YBBjsPs9bih5i3J")
116    p2 = sp.address("tz1ZRjMiF9K9n3S9AcUrTGUzR2okS7dn9KXS")
117    p3 = sp.address("tz1NLJRAAwYdggijWz9EFtX5Dgs95BLfD6mP")
118    g1 = group(5, 5, 2, [(p1, 2), (p2, 8), (p3, 1)])
119    g2 = group(7, 5, 1, [(p1, 7), (p2, 8)])
120    g3 = group(7, 10, 1, [(p3, 10)])
121    contract = sp.record(
122        amount=sp.tez(0),
123        name="demo",
124        owner=p1,
125        thresholdGroupsOK=thresholdGroupsOK,
126        groupsOK=0,
127        thresholdWeight=thresholdWeight,
128        weight=0,
129        groups=sp.list([g1, g2, g3]),
130        ok=False,
131    )
132    return c.build(contract=contract)
133
134
135# Tests
136@sp.add_test()
137def test():
138    alice = sp.test_account("Alice")
139    bob = sp.test_account("Rob")
140    charlie = sp.test_account("Charlie")
141
142    scenario = sp.test_scenario("MultiSig", [t, main])
143    c1 = main.MultiSigFactory()
144    scenario.h1("Multi Sig")
145    scenario.h2("Contract")
146    scenario.h3("Simple multisig factories")
147    scenario += c1
148    scenario.h2("First: define a simple multisig")
149    addMultiSig(c1, thresholdWeight=10, thresholdGroupsOK=2)
150    scenario.h2("Message execution")
151    scenario.h3("A first move")
152    c1.sign(id=alice.address, contractId=0, contractName="demo", _sender=alice)
153    c1.sign(id=bob.address, contractId=0, contractName="demo", _sender=bob)
154
155    scenario.h2("First: define a simple multi-sig")
156    addMultiSig(c1, thresholdWeight=10, thresholdGroupsOK=3)
157    scenario.h2("Message execution")
158    scenario.h3("A first move")
159    c1.sign(id=alice.address, contractId=1, contractName="demo", _sender=alice)
160    c1.sign(id=bob.address, contractId=1, contractName="demo", _sender=bob)
161    scenario.h4("We need a third vote")
162    c1.sign(id=charlie.address, contractId=1, contractName="demo", _sender=charlie)
163    scenario.h4("Final state")
164    scenario.show(c1.data)
165
166    scenario.h3("Multisig factories with payments")
167    c2 = main.MultiSigFactoryWithPayment()
168    scenario += c2
def addMultiSig(c, thresholdWeight, thresholdGroupsOK):
 98def addMultiSig(c, thresholdWeight, thresholdGroupsOK):
 99    # tgroup = c.getStorageType().go('multisigs').go('list').go('groups').go('list')
100    # tparticipant = tgroup.go('participants').go('list')
101    def group(contractWeight, thresholdWeight, thresholdVoters, participants):
102        participants = [
103            sp.record(hasVoted=False, weight=weight, id=id)
104            for (id, weight) in participants
105        ]
106        return sp.record(
107            weight=0,
108            voters=0,
109            contractWeight=contractWeight,
110            thresholdWeight=thresholdWeight,
111            thresholdVoters=thresholdVoters,
112            participants=participants,
113            ok=False,
114        )
115
116    p1 = sp.address("tz1NFevnqBrtcZTZTeKP2YBBjsPs9bih5i3J")
117    p2 = sp.address("tz1ZRjMiF9K9n3S9AcUrTGUzR2okS7dn9KXS")
118    p3 = sp.address("tz1NLJRAAwYdggijWz9EFtX5Dgs95BLfD6mP")
119    g1 = group(5, 5, 2, [(p1, 2), (p2, 8), (p3, 1)])
120    g2 = group(7, 5, 1, [(p1, 7), (p2, 8)])
121    g3 = group(7, 10, 1, [(p3, 10)])
122    contract = sp.record(
123        amount=sp.tez(0),
124        name="demo",
125        owner=p1,
126        thresholdGroupsOK=thresholdGroupsOK,
127        groupsOK=0,
128        thresholdWeight=thresholdWeight,
129        weight=0,
130        groups=sp.list([g1, g2, g3]),
131        ok=False,
132    )
133    return c.build(contract=contract)