1import smartpy as sp
2
3
4@sp.module
5def main():
6 class MultisigView(sp.Contract):
7 """Multiple members vote for arbitrary bytes.
8
9 This contract can be originated with a list of addresses and a number of
10 required votes. Any member can submit as many bytes as they want and vote
11 for active proposals.
12
13 Any bytes that reached the required votes can be confirmed via a view.
14 """
15
16 def __init__(self, members, required_votes):
17 """Constructor
18
19 Args:
20 members (sp.set of sp.address): people who can submit and vote for
21 lambda.
22 required_votes (sp.nat): number of votes required
23 """
24 assert required_votes <= sp.len(
25 members
26 ), "required_votes must be <= len(members)"
27 self.data.proposals = sp.cast(sp.big_map(), sp.big_map[sp.bytes, sp.bool])
28 self.data.votes = sp.cast(
29 sp.big_map(), sp.big_map[sp.bytes, sp.set[sp.address]]
30 )
31 self.data.members = sp.cast(members, sp.set[sp.address])
32 self.data.required_votes = sp.cast(required_votes, sp.nat)
33
34 @sp.entrypoint
35 def submit_proposal(self, bytes):
36 """Submit a new proposal to the vote.
37
38 Submitting a proposal does not imply casting a vote in favour of it.
39
40 Args:
41 bytes(sp.bytes): bytes proposed to vote.
42 Raises:
43 `You are not a member`
44 """
45 assert self.data.members.contains(sp.sender), "You are not a member"
46 self.data.proposals[bytes] = False
47 self.data.votes[bytes] = set()
48
49 @sp.entrypoint
50 def vote_proposal(self, bytes):
51 """Vote for a proposal.
52
53 There is no vote against or pass. If one disagrees with a proposal they
54 can avoid to vote. Warning: old non-voted proposals never become
55 obsolete.
56
57 Args:
58 id(sp.bytes): bytes of the proposal.
59 Raises:
60 `You are not a member`, `Proposal not found`
61 """
62 assert self.data.members.contains(sp.sender), "You are not a member"
63 assert self.data.proposals.contains(bytes), "Proposal not found"
64 self.data.votes[bytes].add(sp.sender)
65 if sp.len(self.data.votes[bytes]) >= self.data.required_votes:
66 self.data.proposals[bytes] = True
67
68 @sp.onchain_view()
69 def is_voted(self, id):
70 """Returns a boolean indicating whether the proposal has been voted on.
71
72 Args:
73 id (sp.bytes): bytes of the proposal
74 Return:
75 (sp.bool): True if the proposal has been voted, False otherwise.
76 """
77 return self.data.proposals.get(id, error="Proposal not found")
78
79
80if "main" in __name__:
81
82 @sp.add_test()
83 def basic_scenario():
84 """A scenario with a vote on the multisigView contract.
85
86 Tests:
87 - Origination
88 - Proposal submission
89 - Proposal vote
90 """
91 sc = sp.test_scenario("MultisigView basic scenario", main)
92 sc.h1("Basic scenario.")
93
94 member1 = sp.test_account("member1")
95 member2 = sp.test_account("member2")
96 member3 = sp.test_account("member3")
97 members = sp.set([member1.address, member2.address, member3.address])
98
99 sc.h2("Origination")
100 c1 = main.MultisigView(members, 2)
101 sc += c1
102
103 sc.h2("submit_proposal")
104 c1.submit_proposal(sp.bytes("0x42"), _sender=member1)
105
106 sc.h2("vote_proposal")
107 c1.vote_proposal(sp.bytes("0x42"), _sender=member1)
108 c1.vote_proposal(sp.bytes("0x42"), _sender=member2)
109
110 # We can check that the proposal has been validated.
111 sc.verify(c1.is_voted(sp.bytes("0x42")))