Expand source code
import smartpy as sp
# This contract has nothing to do with inheritance in the Python sense.
# It's about transfers after a person's death.
@sp.module
def main():
class Inheritance(sp.Contract):
"""Inheritance contract
An owner deposits a certain amount of coins into a contract and regularly
calls the `default` entrypoint to prove that they are still alive.
If the contract is not called since `alive_delta` seconds (1 years in
this example), the heir can withdraw the tez by calling `withdraw`.
At anytime the owner can deposit some tez by using the `default` entrypoint
(or no entrypoint) or withdraw by calling `withdraw`.
"""
def __init__(self, owner, heir, alive_delta, now):
"""
Args:
owner (address): Address that deposits and prove being alive.
heir (address): Address that can withdraw if the owner hasn't proved
being alive.
alive_delta (int): Maximum number of seconds between two calls to `default`.
"""
self.data.owner = owner
self.data.heir = heir
self.data.alive_delta = alive_delta
self.data.timeout = sp.add_seconds(now, alive_delta)
@sp.entrypoint
def default(self):
"""Used by the owner to deposit coins and say that they are still alive.
The `default` entrypoint is also called when doing a transfer without
specifying an entrypoint. This is useful when using a simple wallet or
an app without the ability to specify an entrypoint.
Raises:
`This entrypoint can only be called by the owner.`
"""
assert (
sp.sender == self.data.owner
), "This entrypoint can only be called by the owner."
self.data.timeout = sp.add_seconds(sp.now, self.data.alive_delta)
@sp.entrypoint
def withdraw(self, receiver, amount_):
"""Used by the owner or the heir to withdraw coins.
The heir can only withdraw if the last call was made more than
`alive_delta` seconds ago.
Args:
receiver (address): Receiver of the withdraw.
amount (mutez): Amount withdrawn.
Raises:
`This entrypoint doesn't accept deposits.`
`The owner is still considered alive, you cannot withdraw.`
`Only owner or heir can withdraw.`
"""
assert sp.amount == sp.tez(0), "This entrypoint doesn't accept deposits."
if sp.sender == self.data.heir:
assert (
sp.now > self.data.timeout
), "The owner is still considered alive, you cannot withdraw."
else:
assert sp.sender == self.data.owner, "Only owner or heir can withdraw."
sp.send(receiver, amount_)
owner = sp.test_account("owner").address
heir = sp.test_account("heir").address
ALIVE_DELTA = 366 * 24 * 3600 # 1 leap year
if "templates" not in __name__:
@sp.add_test(name="Inheritance basic scenario", is_default=True)
def basic_scenario():
"""Test of:
- origination.
- deposit.
- owner withdrawal.
- alive confirmation.
- heir withdrawal before timeout.
"""
sc = sp.test_scenario(main)
sc.h1("Basic scenario.")
now = sp.timestamp(0)
sc.h2("Origination.")
c1 = main.Inheritance(owner=owner, heir=heir, alive_delta=ALIVE_DELTA, now=now)
sc += c1
sc.h2("Deposit.")
c1.default().run(sender=owner, amount=sp.tez(1200), now=now)
sc.verify(c1.balance == sp.tez(1200))
sc.verify(c1.data.timeout == now.add_seconds(ALIVE_DELTA))
sc.h2("Owner withdraw.")
now = now.add_minutes(1)
c1.withdraw(receiver=owner, amount_=sp.tez(200)).run(sender=owner, now=now)
sc.h2("Alive confirmation.")
now = now.add_days(360)
c1.default().run(sender=owner, now=now)
sc.h2("Heir withdraw.")
now = now.add_days(367)
c1.withdraw(receiver=heir, amount_=c1.balance).run(sender=heir, now=now)
@sp.add_test(name="Inheritance errors test", is_default=False)
def errors_test():
"""Test of:
- `default`: non-owner calling.
- `withdraw`: not allowed calling.
- `withdraw`: sending tez.
- `withdraw`: heir withdraw before timeout.
"""
sc = sp.test_scenario(main)
sc.h1("Errors tests.")
now = sp.timestamp(0)
sc.h2("Origination.")
c1 = main.Inheritance(owner=owner, heir=heir, alive_delta=ALIVE_DELTA, now=now)
sc += c1
sc.h2("Default: non-owner calling.")
NOT_ALLOWED = sp.test_account("not_allowed").address
c1.default().run(
sender=NOT_ALLOWED,
now=now,
valid=False,
exception="This entrypoint can only be called by the owner.",
)
sc.h2("Withdraw: not allowed calling.")
c1.withdraw(receiver=NOT_ALLOWED, amount_=c1.balance).run(
sender=NOT_ALLOWED,
valid=False,
exception="Only owner or heir can withdraw.",
)
sc.h2("Withdraw: sending tez.")
c1.withdraw(receiver=owner, amount_=c1.balance).run(
sender=owner,
amount=sp.tez(100),
valid=False,
exception="This entrypoint doesn't accept deposits.",
)
sc.h2("Withdraw: heir withdraw before timeout.")
now = now.add_minutes(5)
c1.withdraw(receiver=heir, amount_=c1.balance).run(
sender=heir,
now=now,
valid=False,
exception="The owner is still considered alive, you cannot withdraw.",
)