templates.inheritance

  1import smartpy as sp
  2
  3# This contract has nothing to do with inheritance in the Python sense.
  4# It's about transfers after a person's death.
  5
  6
  7@sp.module
  8def main():
  9    class Inheritance(sp.Contract):
 10        """Inheritance contract
 11
 12        An owner deposits a certain amount of coins into a contract and regularly
 13        calls the `default` entrypoint to prove that they are still alive.
 14
 15        If the contract is not called since `alive_delta` seconds (1 years in
 16        this example), the heir can withdraw the tez by calling `withdraw`.
 17
 18        At anytime the owner can deposit some tez by using the `default` entrypoint
 19        (or no entrypoint) or withdraw by calling `withdraw`.
 20        """
 21
 22        def __init__(self, owner, heir, alive_delta, now):
 23            """
 24            Args:
 25                owner (address): Address that deposits and prove being alive.
 26                heir (address): Address that can withdraw if the owner hasn't proved
 27                    being alive.
 28                alive_delta (int): Maximum number of seconds between two calls to `default`.
 29            """
 30            self.data.owner = owner
 31            self.data.heir = heir
 32            self.data.alive_delta = alive_delta
 33            self.data.timeout = sp.add_seconds(now, alive_delta)
 34
 35        @sp.entrypoint
 36        def default(self):
 37            """Used by the owner to deposit coins and say that they are still alive.
 38
 39            The `default` entrypoint is also called when doing a transfer without
 40            specifying an entrypoint. This is useful when using a simple wallet or
 41            an app without the ability to specify an entrypoint.
 42
 43            Raises:
 44                `This entrypoint can only be called by the owner.`
 45            """
 46            assert (
 47                sp.sender == self.data.owner
 48            ), "This entrypoint can only be called by the owner."
 49
 50            self.data.timeout = sp.add_seconds(sp.now, self.data.alive_delta)
 51
 52        @sp.entrypoint
 53        def withdraw(self, receiver, amount_):
 54            """Used by the owner or the heir to withdraw coins.
 55
 56            The heir can only withdraw if the last call was made more than
 57            `alive_delta` seconds ago.
 58
 59            Args:
 60                receiver (address): Receiver of the withdraw.
 61                amount (mutez): Amount withdrawn.
 62            Raises:
 63                `This entrypoint doesn't accept deposits.`
 64                `The owner is still considered alive, you cannot withdraw.`
 65                `Only owner or heir can withdraw.`
 66            """
 67            assert sp.amount == sp.tez(0), "This entrypoint doesn't accept deposits."
 68            if sp.sender == self.data.heir:
 69                assert (
 70                    sp.now > self.data.timeout
 71                ), "The owner is still considered alive, you cannot withdraw."
 72
 73            else:
 74                assert sp.sender == self.data.owner, "Only owner or heir can withdraw."
 75            sp.send(receiver, amount_)
 76
 77
 78owner = sp.test_account("owner").address
 79heir = sp.test_account("heir").address
 80ALIVE_DELTA = 366 * 24 * 3600  # 1 leap year
 81
 82if "main" in __name__:
 83
 84    @sp.add_test()
 85    def basic_scenario():
 86        """Test of:
 87        - origination.
 88        - deposit.
 89        - owner withdrawal.
 90        - alive confirmation.
 91        - heir withdrawal before timeout.
 92        """
 93        sc = sp.test_scenario("Inheritance basic scenario", main)
 94        sc.h1("Basic scenario.")
 95        now = sp.timestamp(0)
 96
 97        sc.h2("Origination.")
 98        c1 = main.Inheritance(owner=owner, heir=heir, alive_delta=ALIVE_DELTA, now=now)
 99        sc += c1
100
101        sc.h2("Deposit.")
102        c1.default(_sender=owner, _amount=sp.tez(1200), _now=now)
103        sc.verify(c1.balance == sp.tez(1200))
104        sc.verify(c1.data.timeout == now.add_seconds(ALIVE_DELTA))
105
106        sc.h2("Owner withdraw.")
107        now = now.add_minutes(1)
108        c1.withdraw(receiver=owner, amount_=sp.tez(200), _sender=owner, _now=now)
109
110        sc.h2("Alive confirmation.")
111        now = now.add_days(360)
112        c1.default(_sender=owner, _now=now)
113
114        sc.h2("Heir withdraw.")
115        now = now.add_days(367)
116        c1.withdraw(receiver=heir, amount_=c1.balance, _sender=heir, _now=now)
117
118    @sp.add_test()
119    def errors_test():
120        """Test of:
121        - `default`: non-owner calling.
122        - `withdraw`: not allowed calling.
123        - `withdraw`: sending tez.
124        - `withdraw`: heir withdraw before timeout.
125        """
126        sc = sp.test_scenario("Inheritance errors test", main)
127        sc.h1("Errors tests.")
128        now = sp.timestamp(0)
129
130        sc.h2("Origination.")
131        c1 = main.Inheritance(owner=owner, heir=heir, alive_delta=ALIVE_DELTA, now=now)
132        sc += c1
133
134        sc.h2("Default: non-owner calling.")
135        NOT_ALLOWED = sp.test_account("not_allowed").address
136        c1.default(
137            _sender=NOT_ALLOWED,
138            _now=now,
139            _valid=False,
140            _exception="This entrypoint can only be called by the owner.",
141        )
142
143        sc.h2("Withdraw: not allowed calling.")
144        c1.withdraw(
145            receiver=NOT_ALLOWED,
146            amount_=c1.balance,
147            _sender=NOT_ALLOWED,
148            _valid=False,
149            _exception="Only owner or heir can withdraw.",
150        )
151
152        sc.h2("Withdraw: sending tez.")
153        c1.withdraw(
154            receiver=owner,
155            amount_=c1.balance,
156            _sender=owner,
157            _amount=sp.tez(100),
158            _valid=False,
159            _exception="This entrypoint doesn't accept deposits.",
160        )
161
162        sc.h2("Withdraw: heir withdraw before timeout.")
163        now = now.add_minutes(5)
164        c1.withdraw(
165            receiver=heir,
166            amount_=c1.balance,
167            _sender=heir,
168            _now=now,
169            _valid=False,
170            _exception="The owner is still considered alive, you cannot withdraw.",
171        )
owner = (("templates/inheritance.py" 78) attr (("templates/inheritance.py" 78) account_of_seed "owner") "address")
heir = (("templates/inheritance.py" 79) attr (("templates/inheritance.py" 79) account_of_seed "heir") "address")
ALIVE_DELTA = 31622400