templates.multisig_lambda

  1import smartpy as sp
  2
  3
  4@sp.module
  5def main():
  6    operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_operations=True)
  7
  8    class MultisigLambda(sp.Contract):
  9        """Multiple members vote for executing lambdas.
 10
 11        This contract can be originated with a list of addresses and a number of
 12        required votes. Any member can submit as much lambdas as he wants and vote
 13        for active proposals. When a lambda reaches the required votes, its code is
 14        called and the output operations are executed. This allows this contract to
 15        do anything that a contract can do: transferring tokens, managing assets,
 16        administrating another contract...
 17
 18        When a lambda is applied, all submitted lambdas until now are inactivated.
 19        The members can still submit new lambdas.
 20        """
 21
 22        def __init__(self, members, required_votes):
 23            """Constructor
 24
 25            Args:
 26                members (sp.set of sp.address): people who can submit and vote
 27                    for lambda.
 28                required_votes (sp.nat): number of votes required
 29            """
 30            assert required_votes <= sp.len(
 31                members
 32            ), "required_votes must be <= len(members)"
 33            self.data.lambdas = sp.cast(
 34                sp.big_map(), sp.big_map[sp.nat, operation_lambda]
 35            )
 36            self.data.votes = sp.cast(
 37                sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]]
 38            )
 39            self.data.nextId = 0
 40            self.data.inactiveBefore = 0
 41            self.data.members = sp.cast(members, sp.set[sp.address])
 42            self.data.required_votes = sp.cast(required_votes, sp.nat)
 43
 44        @sp.entrypoint
 45        def submit_lambda(self, lambda_):
 46            """Submit a new lambda to the vote.
 47
 48            Submitting a proposal does not imply casting a vote in favour of it.
 49
 50            Args:
 51                lambda_(sp.lambda with operations): lambda proposed to vote.
 52            Raises:
 53                `You are not a member`
 54            """
 55            assert self.data.members.contains(sp.sender), "You are not a member"
 56            self.data.lambdas[self.data.nextId] = lambda_
 57            self.data.votes[self.data.nextId] = set()
 58            self.data.nextId += 1
 59
 60        @sp.entrypoint
 61        def vote_lambda(self, id):
 62            """Vote for a lambda.
 63
 64            Args:
 65                id(sp.nat): id of the lambda to vote for.
 66            Raises:
 67                `You are not a member`, `The lambda is inactive`, `Lambda not found`
 68
 69            There is no vote against or pass. If someone disagrees with a lambda
 70            they can avoid to vote.
 71            """
 72            assert self.data.members.contains(sp.sender), "You are not a member"
 73            assert id >= self.data.inactiveBefore, "The lambda is inactive"
 74            assert self.data.lambdas.contains(id), "Lambda not found"
 75            self.data.votes[id].add(sp.sender)
 76            if sp.len(self.data.votes[id]) >= self.data.required_votes:
 77                self.data.lambdas[id]()
 78                self.data.inactiveBefore = self.data.nextId
 79
 80        @sp.onchain_view()
 81        def get_lambda(self, id):
 82            """Return the corresponding lambda.
 83
 84            Args:
 85                id (sp.nat): id of the lambda to get.
 86
 87            Return:
 88                pair of the lambda and a boolean showing if the lambda is active.
 89            """
 90            return (self.data.lambdas[id], id >= self.data.inactiveBefore)
 91
 92
 93# if "main" in __name__:
 94
 95
 96@sp.module
 97def test():
 98    class Administrated(sp.Contract):
 99        def __init__(self, admin):
100            self.data.admin = admin
101            self.data.value = sp.int(0)
102
103        @sp.entrypoint
104        def set_value(self, value):
105            assert sp.sender == self.data.admin
106            self.data.value = value
107
108
109@sp.add_test()
110def basic_scenario():
111    """Use the multisigLambda as an administrator of an example contract.
112
113    Tests:
114    - Origination
115    - Lambda submission
116    - Lambda vote
117    """
118    sc = sp.test_scenario("MultisigLambda basic scenario", [main, test])
119    sc.h1("Basic scenario.")
120
121    member1 = sp.test_account("member1")
122    member2 = sp.test_account("member2")
123    member3 = sp.test_account("member3")
124    members = sp.set([member1.address, member2.address, member3.address])
125
126    sc.h2("MultisigLambda: origination")
127    c1 = main.MultisigLambda(members, 2)
128    sc += c1
129
130    sc.h2("Administrated: origination")
131    c2 = test.Administrated(c1.address)
132    sc += c2
133
134    sc.h2("MultisigLambda: submit_lambda")
135
136    def set_42(params):
137        administrated = sp.contract(sp.int, c2.address, entrypoint="set_value")
138        sp.transfer(sp.int(42), sp.tez(0), administrated.unwrap_some())
139
140    lambda_ = sp.build_lambda(set_42, with_operations=True)
141    c1.submit_lambda(lambda_, _sender=member1)
142
143    sc.h2("MultisigLambda: vote_lambda")
144    c1.vote_lambda(0, _sender=member1)
145    c1.vote_lambda(0, _sender=member2)
146
147    # We can check that the administrated contract received the transfer.
148    sc.verify(c2.data.value == 42)
basic_scenario = None

Use the multisigLambda as an administrator of an example contract.

Tests:

  • Origination
  • Lambda submission
  • Lambda vote