templates.admin_hand_over

  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")