parent
0cdc5e13ce
commit
4fe2157e36
@ -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; |
||||
} |
||||
} |
@ -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) |
||||
}) |
||||
}) |
Loading…
Reference in new issue