Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Inheritable contract
  • Loading branch information
azavalla committed Nov 30, 2017
commit afb2c0f12d7cbc69923118f44d7f587e8d7252e0
91 changes: 91 additions & 0 deletions contracts/ownership/Inheritable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
pragma solidity ^0.4.11;


import './Ownable.sol';


/**
* @title Inheritable
* @dev The Inheritable contract provides ownership transfer capabilities, in the
* case that the current owner stops "heartbeating". Only the heir can pronounce the
* owner's death.
*/
contract Inheritable2 is Ownable {
address public heir;

// Time window the owner has to notify she is alive.
uint public heartbeatTimeout;

// Timestamp of the owner's death, as pronounced by the heir.
uint public timeOfDeath;


event OwnerPronouncedDead(address indexed owner, address indexed heir, uint indexed timeOfDeath);


/**
* @dev Throw an exception if called by any account other than the heir's.
*/
modifier onlyHeir() {
require(msg.sender == heir);
_;
}


/**
* @notice Create a new Inheritable Contract with heir address 0x0.
* @param _heartbeatTimeout time available for the owner to notify she's alive,
* before the heir can take ownership.
*/
function Inheritable(uint _heartbeatTimeout) public {
heartbeatTimeout = _heartbeatTimeout;
}

function setHeir(address newHeir) public onlyOwner {
heir = newHeir;
}

/**
* @dev set heir = 0x0
*/
function removeHeir() public onlyOwner {
delete(heir);
}

function setHeartbeatTimeout(uint newHeartbeatTimeout) public onlyOwner {
require(ownerLives());
heartbeatTimeout = newHeartbeatTimeout;
}

/**
* @dev Heir can pronounce the owners death. To inherit the ownership, he will
* have to wait for `heartbeatTimeout` seconds.
*/
function pronounceDeath() public onlyHeir {
require(ownerLives());
timeOfDeath = now;
OwnerPronouncedDead(owner, heir, timeOfDeath);
}

/**
* @dev Owner can send a heartbeat if she was mistakenly pronounced dead.
*/
function heartbeat() public onlyOwner {
delete(timeOfDeath);
}

/**
* @dev Allows heir to transfer ownership only if heartbeat has timed out.
*/
function inherit() public onlyHeir {
require(!ownerLives());
require(now >= timeOfDeath + heartbeatTimeout);
OwnershipTransferred(owner, heir);
owner = heir;
delete(timeOfDeath);
}

function ownerLives() internal returns (bool) {
return timeOfDeath == 0;
}
}
139 changes: 139 additions & 0 deletions test/Inheritable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
'use strict'
import { advanceBlock } from './helpers/advanceToBlock'
import increaseTime from './helpers/increaseTime'
import { increaseTimeTo, duration } from './helpers/increaseTime'
import assertJump from './helpers/assertJump'


const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'

const Inheritable = artifacts.require('../contracts/ownership/Inheritable2.sol')

contract('Inheritable', function(accounts) {
let inheritable
let owner

beforeEach(async function() {
inheritable = await Inheritable.new()
owner = await inheritable.owner()
})

it('should start off with an owner, but without heir', async function() {
const heir = await inheritable.heir()

assert.equal(typeof(owner), 'string')
assert.equal(typeof(heir), 'string')
assert.notStrictEqual(
owner, NULL_ADDRESS,
"Owner shouldn't be the null address"
)
assert.isTrue(
heir === NULL_ADDRESS,
"Heir should be the null address"
)
})

it('only owner should set heir', async function() {
const newHeir = accounts[1]
const someRandomAddress = accounts[2]
assert.isTrue(owner !== someRandomAddress)

await inheritable.setHeir(newHeir, {from: owner})
try {
await inheritable.setHeir(newHeir, {from: someRandomAddress})
assert.fail('should have thrown before')
} catch(error) {
assertJump(error)
}
})

it('owner can remove heir', async function() {
const newHeir = accounts[1]
await inheritable.setHeir(newHeir, {from: owner})
let heir = await inheritable.heir()

assert.notStrictEqual(heir, NULL_ADDRESS)
await inheritable.removeHeir()
heir = await inheritable.heir()
assert.isTrue(heir === NULL_ADDRESS)
})

it('owner can set heartbeatTimeout only if she\'s alive', async function() {
const newTimeout = 41414141
await inheritable.setHeartbeatTimeout(newTimeout, {from: owner})

assert.isTrue((await inheritable.heartbeatTimeout()).equals(new web3.BigNumber(newTimeout)))

const heir = accounts[1]
await inheritable.setHeir(heir, {from: owner})
await inheritable.pronounceDeath({from: heir})

try {
await inheritable.setHeartbeatTimeout(newTimeout, {from: owner})
assert.fail('should have thrown before')
} catch(error) {
assertJump(error)
}
})

it('heir can inherit only if owner is dead and timeout was reached', async function() {
const heir = accounts[1]
await inheritable.setHeir(heir, {from: owner})
await inheritable.setHeartbeatTimeout(4141, {from: owner})

try {
await inheritable.inherit({from: heir})
assert.fail('should have thrown before')
} catch(error) {
assertJump(error)
}

await inheritable.pronounceDeath({from: heir})
await increaseTime(1)
try {
await inheritable.inherit({from: heir})
assert.fail('should have thrown before')
} catch(error) {
assertJump(error)
}

await increaseTime(4141)
await inheritable.inherit({from: heir})

})

it('heir can\'t inherit if owner heartbeats', async function() {
const heir = accounts[1]
await inheritable.setHeir(heir, {from: owner})
await inheritable.setHeartbeatTimeout(4141, {from: owner})

await inheritable.pronounceDeath({from: heir})
await inheritable.heartbeat({from: owner})
try {
await inheritable.inherit({from: heir})
assert.fail('should have thrown before')
} catch(error) {
assertJump(error)
}

await inheritable.pronounceDeath({from: heir})
await increaseTime(4141)
await inheritable.heartbeat({from: owner})
try {
await inheritable.inherit({from: heir})
assert.fail('should have thrown before')
} catch(error) {
assertJump(error)
}
})

it('should log owner dead and ownership transfer', async function() {
const heir = accounts[1]
await inheritable.setHeir(heir, {from: owner})
const { logs } = await inheritable.pronounceDeath({from: heir})
const event = logs.find(e => e.event === 'OwnerPronouncedDead')

assert.isTrue(event.args.owner === owner)
assert.isTrue(event.args.heir === heir)
})
})