Module inheritance
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.
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.init(
owner=owner,
heir=heir,
alive_delta=alive_delta,
timeout=now.add_seconds(alive_delta),
)
@sp.entrypoint(new_syntax = True)
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(new_syntax = True)
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()
sc.h1("Basic scenario.")
now = sp.timestamp(0)
sc.h2("Origination.")
c1 = 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()
sc.h1("Errors tests.")
now = sp.timestamp(0)
sc.h2("Origination.")
c1 = 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.",
)
Classes
class Inheritance (owner, heir, alive_delta, now)
-
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 callingwithdraw
.At anytime the owner can deposit some tez by using the
default
entrypoint (or no entrypoint) or withdraw by callingwithdraw
.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
.
Expand source code
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.init( owner=owner, heir=heir, alive_delta=alive_delta, timeout=now.add_seconds(alive_delta), ) @sp.entrypoint(new_syntax = True) 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(new_syntax = True) 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_)
Ancestors
- smartpy.Contract
Methods
def default(self)
-
Entrypoint. 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.
Expand source code
@sp.entrypoint(new_syntax = True) 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)
def withdraw(self, receiver, amount_)
-
Entrypoint. 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.
Expand source code
@sp.entrypoint(new_syntax = True) 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_)