1import smartpy as sp
2
3
4@sp.module
5def main():
6 class AdminHandOver(sp.Contract):
7 """This contract implements a mechanism to switch administrator (an address
8 that with special permission) in a way that prevents the administrator from
9 handing over to an address with a typo or a contract without an interaction
10 code.
11
12 The problem: An administrator hands the contract over to a new administrator
13 and then discovers that they made a typing error in the address.
14 In this case, the contract can no longer be administered.
15
16 The solution: The administrator hand over is a two step process:
17 1. the current administrator adds a candidate admin.
18 2. the new administrator removes the old one.
19 """
20
21 def __init__(self, admins):
22 """
23 Args:
24 admins (sp.set of sp.address): The contract's administrators.
25 """
26 sp.cast(admins, sp.set[sp.address])
27 self.data.admins = admins
28
29 @sp.entrypoint
30 def add_admins(self, admins):
31 """Add new administrators.
32
33 Args:
34 admins (sp.list of sp.address): The new admins.
35 Raises:
36 `Only an admin can call this entrypoint.`
37 """
38 assert self.data.admins.contains(
39 sp.sender
40 ), "Only an admin can call this entrypoint."
41
42 for admin in admins:
43 self.data.admins.add(admin)
44
45 @sp.entrypoint
46 def remove_admins(self, admins):
47 """Remove administrators.
48
49 An admin cannot remove themselves.
50
51 Args:
52 admins (sp.list of sp.address): The admins to remove.
53 Raises:
54 `Only an admin can call this entrypoint.`
55 `An admin cannot remove themselves.`
56 """
57 assert self.data.admins.contains(
58 sp.sender
59 ), "Only an admin can call this entrypoint."
60
61 for admin in admins:
62 assert admin != sp.sender, "An admin cannot remove themselves."
63 self.data.admins.remove(admin)
64
65 @sp.entrypoint
66 def protected(self):
67 """Example of entrypoint reserved to administrators.
68
69 Raises:
70 `Only an admin can call this entrypoint.`
71 """
72 assert self.data.admins.contains(
73 sp.sender
74 ), "Only an admin can call this entrypoint."
75
76
77# This prevent tests from being executed on importation.
78if "main" in __name__:
79 first_admin = sp.test_account("first_admin")
80 new_admin = sp.test_account("new_admin")
81 non_admin = sp.test_account("non_admin")
82
83 @sp.add_test()
84 def basic_scenario():
85 """Test:
86 - Origination
87 - An admin adds a new admin.
88 - The new admin removes the older admin.
89 - The new admin calls the protected entrypoint.
90 """
91 sc = sp.test_scenario("Admin hand over basic scenario", main)
92 sc.h1("Basic scenario.")
93
94 sc.h2("Origination.")
95 c1 = main.AdminHandOver(admins=sp.set([first_admin.address]))
96 sc += c1
97
98 sc.h2("An admin adds a new admin.")
99 c1.add_admins([new_admin.address], _sender=first_admin)
100
101 sc.h2("The new admin removes the older admin.")
102 c1.remove_admins([first_admin.address], _sender=new_admin)
103
104 sc.h2("The new admin calls the protected entrypoint")
105 c1.protected(_sender=new_admin)
106
107 @sp.add_test()
108 def test():
109 sc = sp.test_scenario("Full", main)
110 sc.h1("Full test")
111 sc.h2("Origination")
112 c1 = main.AdminHandOver(admins=sp.set([first_admin.address]))
113 sc += c1
114
115 sc.h2("add_admins")
116 sc.h3("(Failure) Non admin fails to a new admin.")
117 c1.add_admins(
118 [new_admin.address],
119 _sender=non_admin,
120 _valid=False,
121 _exception="Only an admin can call this entrypoint.",
122 )
123 sc.h3("An admin adds a new admin.")
124 c1.add_admins([new_admin.address], _sender=first_admin)
125
126 sc.h2("remove_admins")
127 sc.h3("(Failure) Non admin fails to remove the older admin.")
128 c1.remove_admins(
129 [first_admin.address],
130 _sender=non_admin,
131 _valid=False,
132 _exception="Only an admin can call this entrypoint.",
133 )
134 sc.h3("(Failure) Admin tries to remove themselves.")
135 c1.remove_admins(
136 [first_admin.address],
137 _sender=first_admin,
138 _valid=False,
139 _exception="An admin cannot remove themselves.",
140 )
141 sc.h3("The new admin removes the older admin.")
142 c1.remove_admins([first_admin.address], _sender=new_admin)
143 sc.verify(~c1.data.admins.contains(first_admin.address))
144
145 sc.h2("protected")
146 sc.h3("The new admin calls the protected entrypoint")
147 c1.protected(_sender=new_admin)
148 sc.h3("(Failure) Non admin calls the protected entrypoint")
149 c1.protected(
150 _sender=non_admin,
151 _valid=False,
152 _exception="Only an admin can call this entrypoint.",
153 )
154
155 # @sp.add_test(name="Mutation")
156 # def test():
157 # s = sp.test_scenario()
158 # with s.mutation_test() as mt:
159 # mt.add_scenario("Full")