From 4fe2157e36e9292a7890d6a208ad0bcf95c5c32b Mon Sep 17 00:00:00 2001 From: zava Date: Thu, 23 Nov 2017 17:50:27 -0300 Subject: [PATCH 01/23] Inheritable contract --- contracts/ownership/Inheritable.sol | 91 ++++++++++++++++++ test/Inheritable.js | 139 ++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 contracts/ownership/Inheritable.sol create mode 100644 test/Inheritable.js diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol new file mode 100644 index 00000000000..b5a663b6dd5 --- /dev/null +++ b/contracts/ownership/Inheritable.sol @@ -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; + } +} \ No newline at end of file diff --git a/test/Inheritable.js b/test/Inheritable.js new file mode 100644 index 00000000000..14b49fe711a --- /dev/null +++ b/test/Inheritable.js @@ -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) + }) +}) \ No newline at end of file From 2a560ad82fad8dfe182671d56ed2d2c10f279cfe Mon Sep 17 00:00:00 2001 From: zava Date: Thu, 23 Nov 2017 17:56:11 -0300 Subject: [PATCH 02/23] name fix --- contracts/ownership/Inheritable.sol | 2 +- test/Inheritable.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol index b5a663b6dd5..fe325aa189c 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Inheritable.sol @@ -10,7 +10,7 @@ import './Ownable.sol'; * case that the current owner stops "heartbeating". Only the heir can pronounce the * owner's death. */ -contract Inheritable2 is Ownable { +contract Inheritable is Ownable { address public heir; // Time window the owner has to notify she is alive. diff --git a/test/Inheritable.js b/test/Inheritable.js index 14b49fe711a..143caef4e1a 100644 --- a/test/Inheritable.js +++ b/test/Inheritable.js @@ -7,7 +7,7 @@ import assertJump from './helpers/assertJump' const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' -const Inheritable = artifacts.require('../contracts/ownership/Inheritable2.sol') +const Inheritable = artifacts.require('../contracts/ownership/Inheritable.sol') contract('Inheritable', function(accounts) { let inheritable From b709206f9fb66f9104402591de18616c23024069 Mon Sep 17 00:00:00 2001 From: zava Date: Fri, 24 Nov 2017 17:22:21 -0300 Subject: [PATCH 03/23] fixes --- contracts/ownership/Inheritable.sol | 24 ++++++++++++++-------- test/Inheritable.js | 32 ++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol index fe325aa189c..7b75b44fddc 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Inheritable.sol @@ -13,14 +13,16 @@ import './Ownable.sol'; contract Inheritable is Ownable { address public heir; - // Time window the owner has to notify she is alive. + // Time window the owner has to notify they are 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); + event HeirChanged(address indexed owner, address indexed newHeir); + event OwnerHeartbeated(address indexed owner); + event OwnerPronouncedDead(address indexed owner, address indexed heir, uint timeOfDeath); /** @@ -34,7 +36,7 @@ contract Inheritable is Ownable { /** * @notice Create a new Inheritable Contract with heir address 0x0. - * @param _heartbeatTimeout time available for the owner to notify she's alive, + * @param _heartbeatTimeout time available for the owner to notify they are alive, * before the heir can take ownership. */ function Inheritable(uint _heartbeatTimeout) public { @@ -42,6 +44,8 @@ contract Inheritable is Ownable { } function setHeir(address newHeir) public onlyOwner { + heartbeat(); + HeirChanged(owner, newHeir); heir = newHeir; } @@ -49,7 +53,8 @@ contract Inheritable is Ownable { * @dev set heir = 0x0 */ function removeHeir() public onlyOwner { - delete(heir); + heartbeat(); + heir = 0; } function setHeartbeatTimeout(uint newHeartbeatTimeout) public onlyOwner { @@ -58,20 +63,21 @@ contract Inheritable is Ownable { } /** - * @dev Heir can pronounce the owners death. To inherit the ownership, he will + * @dev Heir can pronounce the owners death. To inherit the ownership, they will * have to wait for `heartbeatTimeout` seconds. */ function pronounceDeath() public onlyHeir { require(ownerLives()); - timeOfDeath = now; OwnerPronouncedDead(owner, heir, timeOfDeath); + timeOfDeath = now; } /** - * @dev Owner can send a heartbeat if she was mistakenly pronounced dead. + * @dev Owner can send a heartbeat if they were mistakenly pronounced dead. */ function heartbeat() public onlyOwner { - delete(timeOfDeath); + OwnerHeartbeated(owner); + timeOfDeath = 0; } /** @@ -82,7 +88,7 @@ contract Inheritable is Ownable { require(now >= timeOfDeath + heartbeatTimeout); OwnershipTransferred(owner, heir); owner = heir; - delete(timeOfDeath); + timeOfDeath = 0; } function ownerLives() internal returns (bool) { diff --git a/test/Inheritable.js b/test/Inheritable.js index 143caef4e1a..d8baec65bbb 100644 --- a/test/Inheritable.js +++ b/test/Inheritable.js @@ -58,7 +58,7 @@ contract('Inheritable', function(accounts) { assert.isTrue(heir === NULL_ADDRESS) }) - it('owner can set heartbeatTimeout only if she\'s alive', async function() { + it('owner can set heartbeatTimeout only if they are alive', async function() { const newTimeout = 41414141 await inheritable.setHeartbeatTimeout(newTimeout, {from: owner}) @@ -127,13 +127,31 @@ contract('Inheritable', function(accounts) { } }) - it('should log owner dead and ownership transfer', async function() { + it('should log events appropriately', 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) + const setHeirLogs = (await inheritable.setHeir(heir, {from: owner})).logs + const setHeirEvent = setHeirLogs.find(e => e.event === 'HeirChanged') + + assert.isTrue(setHeirEvent.args.owner === owner) + assert.isTrue(setHeirEvent.args.newHeir === heir) + + const heartbeatLogs = (await inheritable.heartbeat({from: owner})).logs + const heartbeatEvent = heartbeatLogs.find(e => e.event === 'OwnerHeartbeated') + + assert.isTrue(heartbeatEvent.args.owner === owner) + + const pronounceDeathLogs = (await inheritable.pronounceDeath({from: heir})).logs + const ownerDeadEvent = pronounceDeathLogs.find(e => e.event === 'OwnerPronouncedDead') + + assert.isTrue(ownerDeadEvent.args.owner === owner) + assert.isTrue(ownerDeadEvent.args.heir === heir) + + const inheritLogs = (await inheritable.inherit({from: heir})).logs + const ownershipTransferredEvent = inheritLogs.find(e => e.event === 'OwnershipTransferred') + + assert.isTrue(ownershipTransferredEvent.args.previousOwner === owner) + assert.isTrue(ownershipTransferredEvent.args.newOwner === heir) + }) }) \ No newline at end of file From a613cd03c9e31dbc229cb5fea70a1bd8e993f2e0 Mon Sep 17 00:00:00 2001 From: zava Date: Mon, 27 Nov 2017 01:40:47 -0300 Subject: [PATCH 04/23] added example use-case for Inheritable.sol --- contracts/examples/SimpleSavingsWallet.sol | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 contracts/examples/SimpleSavingsWallet.sol diff --git a/contracts/examples/SimpleSavingsWallet.sol b/contracts/examples/SimpleSavingsWallet.sol new file mode 100644 index 00000000000..f7f55c3cb2b --- /dev/null +++ b/contracts/examples/SimpleSavingsWallet.sol @@ -0,0 +1,34 @@ +pragma solidity ^0.4.11; + +import "../ownership/Inheritable.sol"; + + +/** + * @title SimpleSavingsWallet + * @dev Simplest form of savings wallet that can be inherited if owner dies. + */ +contract SimpleSavingsWallet is Inheritable { + + event Sent(address payee, uint amount, uint balance); + event Received(address payer, uint amount, uint balance); + + + function SimpleSavingsWallet(uint _heartbeatTimeout) Inheritable(_heartbeatTimeout) public {} + + /** + * @dev wallet can receive funds. + */ + function () public payable { + Received(msg.sender, msg.value, this.balance); + } + + /** + * @dev wallet can send funds + */ + function sendTo(address payee, uint amount) public onlyOwner { + require(payee != 0 && payee != address(this)); + require(amount > 0); + payee.transfer(amount); + Sent(payee, amount, this.balance); + } +} \ No newline at end of file From a8afb20c2093b660f563cd593f8311a678e1eb11 Mon Sep 17 00:00:00 2001 From: zava Date: Tue, 28 Nov 2017 22:02:16 -0300 Subject: [PATCH 05/23] Use Inherited event instead of OwnershipTransfered --- contracts/ownership/Inheritable.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol index 7b75b44fddc..60dd03ab379 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Inheritable.sol @@ -23,6 +23,7 @@ contract Inheritable is Ownable { event HeirChanged(address indexed owner, address indexed newHeir); event OwnerHeartbeated(address indexed owner); event OwnerPronouncedDead(address indexed owner, address indexed heir, uint timeOfDeath); + event Inherited(address indexed previousOwner, address indexed newOwner); /** @@ -86,7 +87,7 @@ contract Inheritable is Ownable { function inherit() public onlyHeir { require(!ownerLives()); require(now >= timeOfDeath + heartbeatTimeout); - OwnershipTransferred(owner, heir); + Inherited(owner, heir); owner = heir; timeOfDeath = 0; } From d808e49b51098f93d663fcb8784e9398dc6b3f3b Mon Sep 17 00:00:00 2001 From: zava Date: Tue, 28 Nov 2017 22:03:30 -0300 Subject: [PATCH 06/23] changed 'pronounce' for 'proclaim' --- contracts/ownership/Inheritable.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol index 60dd03ab379..434515f37fe 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Inheritable.sol @@ -22,7 +22,7 @@ contract Inheritable is Ownable { event HeirChanged(address indexed owner, address indexed newHeir); event OwnerHeartbeated(address indexed owner); - event OwnerPronouncedDead(address indexed owner, address indexed heir, uint timeOfDeath); + event OwnerProclaimedDead(address indexed owner, address indexed heir, uint timeOfDeath); event Inherited(address indexed previousOwner, address indexed newOwner); @@ -67,9 +67,9 @@ contract Inheritable is Ownable { * @dev Heir can pronounce the owners death. To inherit the ownership, they will * have to wait for `heartbeatTimeout` seconds. */ - function pronounceDeath() public onlyHeir { + function proclaimDeath() public onlyHeir { require(ownerLives()); - OwnerPronouncedDead(owner, heir, timeOfDeath); + OwnerProclaimedDead(owner, heir, timeOfDeath); timeOfDeath = now; } From 80ae074c576c3dad45ff7021659bcf871ed0290f Mon Sep 17 00:00:00 2001 From: zava Date: Tue, 28 Nov 2017 22:04:10 -0300 Subject: [PATCH 07/23] marked ownerLives as view --- contracts/ownership/Inheritable.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol index 434515f37fe..640e92d7e09 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Inheritable.sol @@ -92,7 +92,7 @@ contract Inheritable is Ownable { timeOfDeath = 0; } - function ownerLives() internal returns (bool) { + function ownerLives() internal view returns (bool) { return timeOfDeath == 0; } -} \ No newline at end of file +} From 560a855dc7e5066cc21360bd214dfb393add23e7 Mon Sep 17 00:00:00 2001 From: zava Date: Tue, 28 Nov 2017 23:04:39 -0300 Subject: [PATCH 08/23] further example documentation --- contracts/examples/SimpleSavingsWallet.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/examples/SimpleSavingsWallet.sol b/contracts/examples/SimpleSavingsWallet.sol index f7f55c3cb2b..29e7d65a43c 100644 --- a/contracts/examples/SimpleSavingsWallet.sol +++ b/contracts/examples/SimpleSavingsWallet.sol @@ -6,6 +6,11 @@ import "../ownership/Inheritable.sol"; /** * @title SimpleSavingsWallet * @dev Simplest form of savings wallet that can be inherited if owner dies. + * In this example, we take a very simple savings wallet providing two operations + * (to send and receive funds) and extend its capabilities by making it Inheritable. + * The account that creates the contract is set as owner, who has the authority to + * choose an heir account. Heir account can reclaim the contract ownership in the + * case that the owner dies. */ contract SimpleSavingsWallet is Inheritable { @@ -31,4 +36,4 @@ contract SimpleSavingsWallet is Inheritable { payee.transfer(amount); Sent(payee, amount, this.balance); } -} \ No newline at end of file +} From 433213537fc93b8c3d8ee5d4658ad16ec13bf736 Mon Sep 17 00:00:00 2001 From: zava Date: Wed, 29 Nov 2017 14:52:22 -0300 Subject: [PATCH 09/23] changed events names on inheritable test --- test/Inheritable.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/Inheritable.js b/test/Inheritable.js index d8baec65bbb..174a40c3853 100644 --- a/test/Inheritable.js +++ b/test/Inheritable.js @@ -66,7 +66,7 @@ contract('Inheritable', function(accounts) { const heir = accounts[1] await inheritable.setHeir(heir, {from: owner}) - await inheritable.pronounceDeath({from: heir}) + await inheritable.proclaimDeath({from: heir}) try { await inheritable.setHeartbeatTimeout(newTimeout, {from: owner}) @@ -88,7 +88,7 @@ contract('Inheritable', function(accounts) { assertJump(error) } - await inheritable.pronounceDeath({from: heir}) + await inheritable.proclaimDeath({from: heir}) await increaseTime(1) try { await inheritable.inherit({from: heir}) @@ -107,7 +107,7 @@ contract('Inheritable', function(accounts) { await inheritable.setHeir(heir, {from: owner}) await inheritable.setHeartbeatTimeout(4141, {from: owner}) - await inheritable.pronounceDeath({from: heir}) + await inheritable.proclaimDeath({from: heir}) await inheritable.heartbeat({from: owner}) try { await inheritable.inherit({from: heir}) @@ -116,7 +116,7 @@ contract('Inheritable', function(accounts) { assertJump(error) } - await inheritable.pronounceDeath({from: heir}) + await inheritable.proclaimDeath({from: heir}) await increaseTime(4141) await inheritable.heartbeat({from: owner}) try { @@ -141,17 +141,17 @@ contract('Inheritable', function(accounts) { assert.isTrue(heartbeatEvent.args.owner === owner) - const pronounceDeathLogs = (await inheritable.pronounceDeath({from: heir})).logs - const ownerDeadEvent = pronounceDeathLogs.find(e => e.event === 'OwnerPronouncedDead') + const proclaimDeathLogs = (await inheritable.proclaimDeath({from: heir})).logs + const ownerDeadEvent = proclaimDeathLogs.find(e => e.event === 'OwnerProclaimedDead') assert.isTrue(ownerDeadEvent.args.owner === owner) assert.isTrue(ownerDeadEvent.args.heir === heir) const inheritLogs = (await inheritable.inherit({from: heir})).logs - const ownershipTransferredEvent = inheritLogs.find(e => e.event === 'OwnershipTransferred') + const ownershipTransferredEvent = inheritLogs.find(e => e.event === 'Inherited') assert.isTrue(ownershipTransferredEvent.args.previousOwner === owner) assert.isTrue(ownershipTransferredEvent.args.newOwner === heir) }) -}) \ No newline at end of file +}) From c7807c5b58c9982fe25d4ccb44abafff40cc6285 Mon Sep 17 00:00:00 2001 From: zava Date: Wed, 29 Nov 2017 15:18:03 -0300 Subject: [PATCH 10/23] changed view for constant --- contracts/ownership/Inheritable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol index 640e92d7e09..88e65cf147f 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Inheritable.sol @@ -92,7 +92,7 @@ contract Inheritable is Ownable { timeOfDeath = 0; } - function ownerLives() internal view returns (bool) { + function ownerLives() internal constant returns (bool) { return timeOfDeath == 0; } } From 5716492ebd52895a2d51df3877125cb02d002975 Mon Sep 17 00:00:00 2001 From: zava Date: Wed, 29 Nov 2017 15:46:26 -0300 Subject: [PATCH 11/23] setHeartbeatTimeout: public --> internal --- contracts/ownership/Inheritable.sol | 12 ++++++------ test/Inheritable.js | 24 ++---------------------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol index 88e65cf147f..be28e000f62 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Inheritable.sol @@ -41,7 +41,7 @@ contract Inheritable is Ownable { * before the heir can take ownership. */ function Inheritable(uint _heartbeatTimeout) public { - heartbeatTimeout = _heartbeatTimeout; + setHeartbeatTimeout(_heartbeatTimeout); } function setHeir(address newHeir) public onlyOwner { @@ -58,11 +58,6 @@ contract Inheritable is Ownable { heir = 0; } - function setHeartbeatTimeout(uint newHeartbeatTimeout) public onlyOwner { - require(ownerLives()); - heartbeatTimeout = newHeartbeatTimeout; - } - /** * @dev Heir can pronounce the owners death. To inherit the ownership, they will * have to wait for `heartbeatTimeout` seconds. @@ -92,6 +87,11 @@ contract Inheritable is Ownable { timeOfDeath = 0; } + function setHeartbeatTimeout(uint newHeartbeatTimeout) internal onlyOwner { + require(ownerLives()); + heartbeatTimeout = newHeartbeatTimeout; + } + function ownerLives() internal constant returns (bool) { return timeOfDeath == 0; } diff --git a/test/Inheritable.js b/test/Inheritable.js index 174a40c3853..44536ca9bfc 100644 --- a/test/Inheritable.js +++ b/test/Inheritable.js @@ -14,7 +14,7 @@ contract('Inheritable', function(accounts) { let owner beforeEach(async function() { - inheritable = await Inheritable.new() + inheritable = await Inheritable.new(4141) owner = await inheritable.owner() }) @@ -58,28 +58,9 @@ contract('Inheritable', function(accounts) { assert.isTrue(heir === NULL_ADDRESS) }) - it('owner can set heartbeatTimeout only if they are 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.proclaimDeath({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}) @@ -99,13 +80,11 @@ contract('Inheritable', function(accounts) { 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.proclaimDeath({from: heir}) await inheritable.heartbeat({from: owner}) @@ -147,6 +126,7 @@ contract('Inheritable', function(accounts) { assert.isTrue(ownerDeadEvent.args.owner === owner) assert.isTrue(ownerDeadEvent.args.heir === heir) + await increaseTime(4141) const inheritLogs = (await inheritable.inherit({from: heir})).logs const ownershipTransferredEvent = inheritLogs.find(e => e.event === 'Inherited') From e5960465a72b97bf3218ba5b85ab2cb3d6f4b1c8 Mon Sep 17 00:00:00 2001 From: zava Date: Wed, 29 Nov 2017 15:46:58 -0300 Subject: [PATCH 12/23] owner can't be set as heir --- contracts/ownership/Inheritable.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol index be28e000f62..cce710d1f02 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Inheritable.sol @@ -45,6 +45,7 @@ contract Inheritable is Ownable { } function setHeir(address newHeir) public onlyOwner { + require(newHeir != owner); heartbeat(); HeirChanged(owner, newHeir); heir = newHeir; From 82c85121bba8e84cb6096f48303281cc746c6974 Mon Sep 17 00:00:00 2001 From: zava Date: Wed, 29 Nov 2017 22:42:33 -0300 Subject: [PATCH 13/23] changed Inherited event for OwnershipTransfered --- contracts/ownership/Inheritable.sol | 3 +-- test/Inheritable.js | 2 +- test/SimpleSavingsWallet.js | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 test/SimpleSavingsWallet.js diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Inheritable.sol index cce710d1f02..508f6beeaa5 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Inheritable.sol @@ -23,7 +23,6 @@ contract Inheritable is Ownable { event HeirChanged(address indexed owner, address indexed newHeir); event OwnerHeartbeated(address indexed owner); event OwnerProclaimedDead(address indexed owner, address indexed heir, uint timeOfDeath); - event Inherited(address indexed previousOwner, address indexed newOwner); /** @@ -83,7 +82,7 @@ contract Inheritable is Ownable { function inherit() public onlyHeir { require(!ownerLives()); require(now >= timeOfDeath + heartbeatTimeout); - Inherited(owner, heir); + OwnershipTransferred(owner, heir); owner = heir; timeOfDeath = 0; } diff --git a/test/Inheritable.js b/test/Inheritable.js index 44536ca9bfc..147035c91ce 100644 --- a/test/Inheritable.js +++ b/test/Inheritable.js @@ -128,7 +128,7 @@ contract('Inheritable', function(accounts) { await increaseTime(4141) const inheritLogs = (await inheritable.inherit({from: heir})).logs - const ownershipTransferredEvent = inheritLogs.find(e => e.event === 'Inherited') + const ownershipTransferredEvent = inheritLogs.find(e => e.event === 'OwnershipTransferred') assert.isTrue(ownershipTransferredEvent.args.previousOwner === owner) assert.isTrue(ownershipTransferredEvent.args.newOwner === heir) diff --git a/test/SimpleSavingsWallet.js b/test/SimpleSavingsWallet.js new file mode 100644 index 00000000000..c51972ec811 --- /dev/null +++ b/test/SimpleSavingsWallet.js @@ -0,0 +1,16 @@ +'use strict' + +const SimpleSavingsWallet = artifacts.require('../contracts/examples/SimpleSavingsWallet.sol') + +contract('SimpleSavingsWallet', function(accounts) { + let savingsWallet + let owner + + beforeEach(async function() { + savingsWallet = await SimpleSavingsWallet.new(4141) + owner = await inheritable.owner() + }) + + it('should receive funds', async function() { + await web3.eth.sendTransaction({from: owner, to: this.contract.address, value: amount}) + }) From 46736da489d4252ea2adf05468b33b8c44beab72 Mon Sep 17 00:00:00 2001 From: zava Date: Wed, 29 Nov 2017 23:36:40 -0300 Subject: [PATCH 14/23] added test for SimpleSavingsWallet --- test/SimpleSavingsWallet.js | 40 +++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/test/SimpleSavingsWallet.js b/test/SimpleSavingsWallet.js index c51972ec811..2722dc1b0af 100644 --- a/test/SimpleSavingsWallet.js +++ b/test/SimpleSavingsWallet.js @@ -1,4 +1,5 @@ 'use strict' +import assertJump from './helpers/assertJump' const SimpleSavingsWallet = artifacts.require('../contracts/examples/SimpleSavingsWallet.sol') @@ -6,11 +7,46 @@ contract('SimpleSavingsWallet', function(accounts) { let savingsWallet let owner + const paymentAmount = 4242 + beforeEach(async function() { savingsWallet = await SimpleSavingsWallet.new(4141) - owner = await inheritable.owner() + owner = await savingsWallet.owner() }) it('should receive funds', async function() { - await web3.eth.sendTransaction({from: owner, to: this.contract.address, value: amount}) + await web3.eth.sendTransaction({from: owner, to: savingsWallet.address, value: paymentAmount}) + assert.isTrue( + (new web3.BigNumber(paymentAmount)).equals(web3.eth.getBalance(savingsWallet.address)) + ) }) + + it('owner can send funds', async function() { + // Receive payment so we have some money to spend. + await web3.eth.sendTransaction({from: accounts[9], to: savingsWallet.address, value: 1000000}) + try { + await savingsWallet.sendTo(0, paymentAmount, {from: owner}) + assert.fail('should have thrown before') + } catch(error) { + assertJump(error) + } + try { + await savingsWallet.sendTo(savingsWallet.address, paymentAmount, {from: owner}) + assert.fail('should have thrown before') + } catch(error) { + assertJump(error) + } + try { + await savingsWallet.sendTo(accounts[1], 0, {from: owner}) + assert.fail('should have thrown before') + } catch(error) { + assertJump(error) + } + + const balance = web3.eth.getBalance(accounts[1]) + await savingsWallet.sendTo(accounts[1], paymentAmount, {from: owner}) + assert.isTrue( + balance.plus(paymentAmount).equals(web3.eth.getBalance(accounts[1])) + ) + }) +}) From c70ee937e6167bf1d326ddf32313535dbdcc7271 Mon Sep 17 00:00:00 2001 From: zava Date: Thu, 30 Nov 2017 12:30:52 -0300 Subject: [PATCH 15/23] [Inheritable.js] replace assertJump for expectThrow --- test/Inheritable.js | 42 +++++++----------------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/test/Inheritable.js b/test/Inheritable.js index 147035c91ce..1733a94dbfc 100644 --- a/test/Inheritable.js +++ b/test/Inheritable.js @@ -1,9 +1,6 @@ 'use strict' -import { advanceBlock } from './helpers/advanceToBlock' import increaseTime from './helpers/increaseTime' -import { increaseTimeTo, duration } from './helpers/increaseTime' -import assertJump from './helpers/assertJump' - +import expectThrow from './helpers/expectThrow'; const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' @@ -39,12 +36,7 @@ contract('Inheritable', function(accounts) { 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) - } + await expectThrow(inheritable.setHeir(newHeir, {from: someRandomAddress})) }) it('owner can remove heir', async function() { @@ -61,25 +53,15 @@ contract('Inheritable', function(accounts) { 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}) - - try { - await inheritable.inherit({from: heir}) - assert.fail('should have thrown before') - } catch(error) { - assertJump(error) - } + await expectThrow(inheritable.inherit({from: heir})) await inheritable.proclaimDeath({from: heir}) await increaseTime(1) - try { - await inheritable.inherit({from: heir}) - assert.fail('should have thrown before') - } catch(error) { - assertJump(error) - } + await expectThrow(inheritable.inherit({from: heir})) await increaseTime(4141) await inheritable.inherit({from: heir}) + assert.isTrue(await inheritable.heir() === heir) }) it('heir can\'t inherit if owner heartbeats', async function() { @@ -88,22 +70,12 @@ contract('Inheritable', function(accounts) { await inheritable.proclaimDeath({from: heir}) await inheritable.heartbeat({from: owner}) - try { - await inheritable.inherit({from: heir}) - assert.fail('should have thrown before') - } catch(error) { - assertJump(error) - } + await expectThrow(inheritable.inherit({from: heir})) await inheritable.proclaimDeath({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) - } + await expectThrow(inheritable.inherit({from: heir})) }) it('should log events appropriately', async function() { From fe712c678ab286525ed4c2debf10300f581959e6 Mon Sep 17 00:00:00 2001 From: zava Date: Thu, 30 Nov 2017 12:35:00 -0300 Subject: [PATCH 16/23] [SimpleSavingsWallet.js] replace assertJump for expectThrow --- test/SimpleSavingsWallet.js | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/test/SimpleSavingsWallet.js b/test/SimpleSavingsWallet.js index 2722dc1b0af..8ae34d58505 100644 --- a/test/SimpleSavingsWallet.js +++ b/test/SimpleSavingsWallet.js @@ -1,5 +1,5 @@ 'use strict' -import assertJump from './helpers/assertJump' +import expectThrow from './helpers/expectThrow'; const SimpleSavingsWallet = artifacts.require('../contracts/examples/SimpleSavingsWallet.sol') @@ -24,29 +24,12 @@ contract('SimpleSavingsWallet', function(accounts) { it('owner can send funds', async function() { // Receive payment so we have some money to spend. await web3.eth.sendTransaction({from: accounts[9], to: savingsWallet.address, value: 1000000}) - try { - await savingsWallet.sendTo(0, paymentAmount, {from: owner}) - assert.fail('should have thrown before') - } catch(error) { - assertJump(error) - } - try { - await savingsWallet.sendTo(savingsWallet.address, paymentAmount, {from: owner}) - assert.fail('should have thrown before') - } catch(error) { - assertJump(error) - } - try { - await savingsWallet.sendTo(accounts[1], 0, {from: owner}) - assert.fail('should have thrown before') - } catch(error) { - assertJump(error) - } + await expectThrow(savingsWallet.sendTo(0, paymentAmount, {from: owner})) + await expectThrow(savingsWallet.sendTo(savingsWallet.address, paymentAmount, {from: owner})) + await expectThrow(savingsWallet.sendTo(accounts[1], 0, {from: owner})) const balance = web3.eth.getBalance(accounts[1]) await savingsWallet.sendTo(accounts[1], paymentAmount, {from: owner}) - assert.isTrue( - balance.plus(paymentAmount).equals(web3.eth.getBalance(accounts[1])) - ) + assert.isTrue(balance.plus(paymentAmount).equals(web3.eth.getBalance(accounts[1]))) }) }) From 52b6181d78e9149d239af85c2f97179613d76fa1 Mon Sep 17 00:00:00 2001 From: zava Date: Thu, 30 Nov 2017 18:25:23 -0300 Subject: [PATCH 17/23] renamed Inheritable --> Heritable --- contracts/examples/SimpleSavingsWallet.sol | 11 ++-- .../{Inheritable.sol => Heritable.sol} | 14 ++-- test/{Inheritable.js => Heritable.js} | 64 +++++++++---------- 3 files changed, 45 insertions(+), 44 deletions(-) rename contracts/ownership/{Inheritable.sol => Heritable.sol} (84%) rename test/{Inheritable.js => Heritable.js} (51%) diff --git a/contracts/examples/SimpleSavingsWallet.sol b/contracts/examples/SimpleSavingsWallet.sol index 29e7d65a43c..e6d11d5a208 100644 --- a/contracts/examples/SimpleSavingsWallet.sol +++ b/contracts/examples/SimpleSavingsWallet.sol @@ -1,24 +1,25 @@ pragma solidity ^0.4.11; -import "../ownership/Inheritable.sol"; +import "../ownership/Heritable.sol"; /** * @title SimpleSavingsWallet - * @dev Simplest form of savings wallet that can be inherited if owner dies. + * @dev Simplest form of savings wallet whose ownership can be claimed by a heir + * if owner dies. * In this example, we take a very simple savings wallet providing two operations - * (to send and receive funds) and extend its capabilities by making it Inheritable. + * (to send and receive funds) and extend its capabilities by making it Heritable. * The account that creates the contract is set as owner, who has the authority to * choose an heir account. Heir account can reclaim the contract ownership in the * case that the owner dies. */ -contract SimpleSavingsWallet is Inheritable { +contract SimpleSavingsWallet is Heritable { event Sent(address payee, uint amount, uint balance); event Received(address payer, uint amount, uint balance); - function SimpleSavingsWallet(uint _heartbeatTimeout) Inheritable(_heartbeatTimeout) public {} + function SimpleSavingsWallet(uint _heartbeatTimeout) Heritable(_heartbeatTimeout) public {} /** * @dev wallet can receive funds. diff --git a/contracts/ownership/Inheritable.sol b/contracts/ownership/Heritable.sol similarity index 84% rename from contracts/ownership/Inheritable.sol rename to contracts/ownership/Heritable.sol index 508f6beeaa5..b0982879b30 100644 --- a/contracts/ownership/Inheritable.sol +++ b/contracts/ownership/Heritable.sol @@ -5,12 +5,12 @@ import './Ownable.sol'; /** - * @title Inheritable - * @dev The Inheritable contract provides ownership transfer capabilities, in the + * @title Heritable + * @dev The Heritable contract provides ownership transfer capabilities, in the * case that the current owner stops "heartbeating". Only the heir can pronounce the * owner's death. */ -contract Inheritable is Ownable { +contract Heritable is Ownable { address public heir; // Time window the owner has to notify they are alive. @@ -35,11 +35,11 @@ contract Inheritable is Ownable { /** - * @notice Create a new Inheritable Contract with heir address 0x0. + * @notice Create a new Heritable Contract with heir address 0x0. * @param _heartbeatTimeout time available for the owner to notify they are alive, * before the heir can take ownership. */ - function Inheritable(uint _heartbeatTimeout) public { + function Heritable(uint _heartbeatTimeout) public { setHeartbeatTimeout(_heartbeatTimeout); } @@ -59,7 +59,7 @@ contract Inheritable is Ownable { } /** - * @dev Heir can pronounce the owners death. To inherit the ownership, they will + * @dev Heir can pronounce the owners death. To claim the ownership, they will * have to wait for `heartbeatTimeout` seconds. */ function proclaimDeath() public onlyHeir { @@ -79,7 +79,7 @@ contract Inheritable is Ownable { /** * @dev Allows heir to transfer ownership only if heartbeat has timed out. */ - function inherit() public onlyHeir { + function claimHeirOwnership() public onlyHeir { require(!ownerLives()); require(now >= timeOfDeath + heartbeatTimeout); OwnershipTransferred(owner, heir); diff --git a/test/Inheritable.js b/test/Heritable.js similarity index 51% rename from test/Inheritable.js rename to test/Heritable.js index 1733a94dbfc..0b7893c2e08 100644 --- a/test/Inheritable.js +++ b/test/Heritable.js @@ -4,19 +4,19 @@ import expectThrow from './helpers/expectThrow'; const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' -const Inheritable = artifacts.require('../contracts/ownership/Inheritable.sol') +const Heritable = artifacts.require('../contracts/ownership/Heritable.sol') -contract('Inheritable', function(accounts) { - let inheritable +contract('Heritable', function(accounts) { + let heritable let owner beforeEach(async function() { - inheritable = await Inheritable.new(4141) - owner = await inheritable.owner() + heritable = await Heritable.new(4141) + owner = await heritable.owner() }) it('should start off with an owner, but without heir', async function() { - const heir = await inheritable.heir() + const heir = await heritable.heir() assert.equal(typeof(owner), 'string') assert.equal(typeof(heir), 'string') @@ -35,72 +35,72 @@ contract('Inheritable', function(accounts) { const someRandomAddress = accounts[2] assert.isTrue(owner !== someRandomAddress) - await inheritable.setHeir(newHeir, {from: owner}) - await expectThrow(inheritable.setHeir(newHeir, {from: someRandomAddress})) + await heritable.setHeir(newHeir, {from: owner}) + await expectThrow(heritable.setHeir(newHeir, {from: someRandomAddress})) }) it('owner can remove heir', async function() { const newHeir = accounts[1] - await inheritable.setHeir(newHeir, {from: owner}) - let heir = await inheritable.heir() + await heritable.setHeir(newHeir, {from: owner}) + let heir = await heritable.heir() assert.notStrictEqual(heir, NULL_ADDRESS) - await inheritable.removeHeir() - heir = await inheritable.heir() + await heritable.removeHeir() + heir = await heritable.heir() assert.isTrue(heir === NULL_ADDRESS) }) - it('heir can inherit only if owner is dead and timeout was reached', async function() { + it('heir can claim ownership only if owner is dead and timeout was reached', async function() { const heir = accounts[1] - await inheritable.setHeir(heir, {from: owner}) - await expectThrow(inheritable.inherit({from: heir})) + await heritable.setHeir(heir, {from: owner}) + await expectThrow(heritable.claimHeirOwnership({from: heir})) - await inheritable.proclaimDeath({from: heir}) + await heritable.proclaimDeath({from: heir}) await increaseTime(1) - await expectThrow(inheritable.inherit({from: heir})) + await expectThrow(heritable.claimHeirOwnership({from: heir})) await increaseTime(4141) - await inheritable.inherit({from: heir}) - assert.isTrue(await inheritable.heir() === heir) + await heritable.claimHeirOwnership({from: heir}) + assert.isTrue(await heritable.heir() === heir) }) - it('heir can\'t inherit if owner heartbeats', async function() { + it('heir can\'t claim ownership if owner heartbeats', async function() { const heir = accounts[1] - await inheritable.setHeir(heir, {from: owner}) + await heritable.setHeir(heir, {from: owner}) - await inheritable.proclaimDeath({from: heir}) - await inheritable.heartbeat({from: owner}) - await expectThrow(inheritable.inherit({from: heir})) + await heritable.proclaimDeath({from: heir}) + await heritable.heartbeat({from: owner}) + await expectThrow(heritable.claimHeirOwnership({from: heir})) - await inheritable.proclaimDeath({from: heir}) + await heritable.proclaimDeath({from: heir}) await increaseTime(4141) - await inheritable.heartbeat({from: owner}) - await expectThrow(inheritable.inherit({from: heir})) + await heritable.heartbeat({from: owner}) + await expectThrow(heritable.claimHeirOwnership({from: heir})) }) it('should log events appropriately', async function() { const heir = accounts[1] - const setHeirLogs = (await inheritable.setHeir(heir, {from: owner})).logs + const setHeirLogs = (await heritable.setHeir(heir, {from: owner})).logs const setHeirEvent = setHeirLogs.find(e => e.event === 'HeirChanged') assert.isTrue(setHeirEvent.args.owner === owner) assert.isTrue(setHeirEvent.args.newHeir === heir) - const heartbeatLogs = (await inheritable.heartbeat({from: owner})).logs + const heartbeatLogs = (await heritable.heartbeat({from: owner})).logs const heartbeatEvent = heartbeatLogs.find(e => e.event === 'OwnerHeartbeated') assert.isTrue(heartbeatEvent.args.owner === owner) - const proclaimDeathLogs = (await inheritable.proclaimDeath({from: heir})).logs + const proclaimDeathLogs = (await heritable.proclaimDeath({from: heir})).logs const ownerDeadEvent = proclaimDeathLogs.find(e => e.event === 'OwnerProclaimedDead') assert.isTrue(ownerDeadEvent.args.owner === owner) assert.isTrue(ownerDeadEvent.args.heir === heir) await increaseTime(4141) - const inheritLogs = (await inheritable.inherit({from: heir})).logs - const ownershipTransferredEvent = inheritLogs.find(e => e.event === 'OwnershipTransferred') + const claimHeirOwnershipLogs = (await heritable.claimHeirOwnership({from: heir})).logs + const ownershipTransferredEvent = claimHeirOwnershipLogs.find(e => e.event === 'OwnershipTransferred') assert.isTrue(ownershipTransferredEvent.args.previousOwner === owner) assert.isTrue(ownershipTransferredEvent.args.newOwner === heir) From 51c2c504376c1d92fc07d273fbeea5cf6dc60d06 Mon Sep 17 00:00:00 2001 From: zava Date: Thu, 30 Nov 2017 18:29:38 -0300 Subject: [PATCH 18/23] [Heritable] ownerLives(): constant --> view --- contracts/ownership/Heritable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ownership/Heritable.sol b/contracts/ownership/Heritable.sol index b0982879b30..d82e09d5a59 100644 --- a/contracts/ownership/Heritable.sol +++ b/contracts/ownership/Heritable.sol @@ -92,7 +92,7 @@ contract Heritable is Ownable { heartbeatTimeout = newHeartbeatTimeout; } - function ownerLives() internal constant returns (bool) { + function ownerLives() internal view returns (bool) { return timeOfDeath == 0; } } From 5ea9bd44a1726551526405e32e27b212e1a174b4 Mon Sep 17 00:00:00 2001 From: zava Date: Thu, 30 Nov 2017 18:35:40 -0300 Subject: [PATCH 19/23] [Heritable] added HeirOwnershipClaimed event --- contracts/ownership/Heritable.sol | 2 ++ test/Heritable.js | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/ownership/Heritable.sol b/contracts/ownership/Heritable.sol index d82e09d5a59..11aa22f8cb5 100644 --- a/contracts/ownership/Heritable.sol +++ b/contracts/ownership/Heritable.sol @@ -23,6 +23,7 @@ contract Heritable is Ownable { event HeirChanged(address indexed owner, address indexed newHeir); event OwnerHeartbeated(address indexed owner); event OwnerProclaimedDead(address indexed owner, address indexed heir, uint timeOfDeath); + event HeirOwnershipClaimed(address indexed previousOwner, address indexed newOwner); /** @@ -83,6 +84,7 @@ contract Heritable is Ownable { require(!ownerLives()); require(now >= timeOfDeath + heartbeatTimeout); OwnershipTransferred(owner, heir); + HeirOwnershipClaimed(owner, heir); owner = heir; timeOfDeath = 0; } diff --git a/test/Heritable.js b/test/Heritable.js index 0b7893c2e08..028abf92516 100644 --- a/test/Heritable.js +++ b/test/Heritable.js @@ -67,7 +67,7 @@ contract('Heritable', function(accounts) { it('heir can\'t claim ownership if owner heartbeats', async function() { const heir = accounts[1] await heritable.setHeir(heir, {from: owner}) - + await heritable.proclaimDeath({from: heir}) await heritable.heartbeat({from: owner}) await expectThrow(heritable.claimHeirOwnership({from: heir})) @@ -101,9 +101,12 @@ contract('Heritable', function(accounts) { await increaseTime(4141) const claimHeirOwnershipLogs = (await heritable.claimHeirOwnership({from: heir})).logs const ownershipTransferredEvent = claimHeirOwnershipLogs.find(e => e.event === 'OwnershipTransferred') + const heirOwnershipClaimedEvent = claimHeirOwnershipLogs.find(e => e.event === 'HeirOwnershipClaimed') assert.isTrue(ownershipTransferredEvent.args.previousOwner === owner) assert.isTrue(ownershipTransferredEvent.args.newOwner === heir) + assert.isTrue(heirOwnershipClaimedEvent.args.previousOwner === owner) + assert.isTrue(heirOwnershipClaimedEvent.args.newOwner === heir) }) }) From 3009553925c6b152a422c37e9dadd5586ff461e7 Mon Sep 17 00:00:00 2001 From: Alejandro Santander Date: Tue, 16 Jan 2018 13:33:47 -0300 Subject: [PATCH 20/23] Update test names and js style --- test/Heritable.js | 112 ------------------------------- test/Heritable.test.js | 110 ++++++++++++++++++++++++++++++ test/SimpleSavingsWallet.js | 35 ---------- test/SimpleSavingsWallet.test.js | 33 +++++++++ 4 files changed, 143 insertions(+), 147 deletions(-) delete mode 100644 test/Heritable.js create mode 100644 test/Heritable.test.js delete mode 100644 test/SimpleSavingsWallet.js create mode 100644 test/SimpleSavingsWallet.test.js diff --git a/test/Heritable.js b/test/Heritable.js deleted file mode 100644 index 028abf92516..00000000000 --- a/test/Heritable.js +++ /dev/null @@ -1,112 +0,0 @@ -'use strict' -import increaseTime from './helpers/increaseTime' -import expectThrow from './helpers/expectThrow'; - -const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' - -const Heritable = artifacts.require('../contracts/ownership/Heritable.sol') - -contract('Heritable', function(accounts) { - let heritable - let owner - - beforeEach(async function() { - heritable = await Heritable.new(4141) - owner = await heritable.owner() - }) - - it('should start off with an owner, but without heir', async function() { - const heir = await heritable.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 heritable.setHeir(newHeir, {from: owner}) - await expectThrow(heritable.setHeir(newHeir, {from: someRandomAddress})) - }) - - it('owner can remove heir', async function() { - const newHeir = accounts[1] - await heritable.setHeir(newHeir, {from: owner}) - let heir = await heritable.heir() - - assert.notStrictEqual(heir, NULL_ADDRESS) - await heritable.removeHeir() - heir = await heritable.heir() - assert.isTrue(heir === NULL_ADDRESS) - }) - - it('heir can claim ownership only if owner is dead and timeout was reached', async function() { - const heir = accounts[1] - await heritable.setHeir(heir, {from: owner}) - await expectThrow(heritable.claimHeirOwnership({from: heir})) - - await heritable.proclaimDeath({from: heir}) - await increaseTime(1) - await expectThrow(heritable.claimHeirOwnership({from: heir})) - - await increaseTime(4141) - await heritable.claimHeirOwnership({from: heir}) - assert.isTrue(await heritable.heir() === heir) - }) - - it('heir can\'t claim ownership if owner heartbeats', async function() { - const heir = accounts[1] - await heritable.setHeir(heir, {from: owner}) - - await heritable.proclaimDeath({from: heir}) - await heritable.heartbeat({from: owner}) - await expectThrow(heritable.claimHeirOwnership({from: heir})) - - await heritable.proclaimDeath({from: heir}) - await increaseTime(4141) - await heritable.heartbeat({from: owner}) - await expectThrow(heritable.claimHeirOwnership({from: heir})) - }) - - it('should log events appropriately', async function() { - const heir = accounts[1] - - const setHeirLogs = (await heritable.setHeir(heir, {from: owner})).logs - const setHeirEvent = setHeirLogs.find(e => e.event === 'HeirChanged') - - assert.isTrue(setHeirEvent.args.owner === owner) - assert.isTrue(setHeirEvent.args.newHeir === heir) - - const heartbeatLogs = (await heritable.heartbeat({from: owner})).logs - const heartbeatEvent = heartbeatLogs.find(e => e.event === 'OwnerHeartbeated') - - assert.isTrue(heartbeatEvent.args.owner === owner) - - const proclaimDeathLogs = (await heritable.proclaimDeath({from: heir})).logs - const ownerDeadEvent = proclaimDeathLogs.find(e => e.event === 'OwnerProclaimedDead') - - assert.isTrue(ownerDeadEvent.args.owner === owner) - assert.isTrue(ownerDeadEvent.args.heir === heir) - - await increaseTime(4141) - const claimHeirOwnershipLogs = (await heritable.claimHeirOwnership({from: heir})).logs - const ownershipTransferredEvent = claimHeirOwnershipLogs.find(e => e.event === 'OwnershipTransferred') - const heirOwnershipClaimedEvent = claimHeirOwnershipLogs.find(e => e.event === 'HeirOwnershipClaimed') - - assert.isTrue(ownershipTransferredEvent.args.previousOwner === owner) - assert.isTrue(ownershipTransferredEvent.args.newOwner === heir) - assert.isTrue(heirOwnershipClaimedEvent.args.previousOwner === owner) - assert.isTrue(heirOwnershipClaimedEvent.args.newOwner === heir) - - }) -}) diff --git a/test/Heritable.test.js b/test/Heritable.test.js new file mode 100644 index 00000000000..033fe217d5f --- /dev/null +++ b/test/Heritable.test.js @@ -0,0 +1,110 @@ +import increaseTime from './helpers/increaseTime'; +import expectThrow from './helpers/expectThrow'; + +const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; + +const Heritable = artifacts.require('../contracts/ownership/Heritable.sol'); + +contract('Heritable', function (accounts) { + let heritable; + let owner; + + beforeEach(async function () { + heritable = await Heritable.new(4141); + owner = await heritable.owner(); + }); + + it('should start off with an owner, but without heir', async function () { + const heir = await heritable.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 heritable.setHeir(newHeir, { from: owner }); + await expectThrow(heritable.setHeir(newHeir, { from: someRandomAddress })); + }); + + it('owner can remove heir', async function () { + const newHeir = accounts[1]; + await heritable.setHeir(newHeir, { from: owner }); + let heir = await heritable.heir(); + + assert.notStrictEqual(heir, NULL_ADDRESS); + await heritable.removeHeir(); + heir = await heritable.heir(); + assert.isTrue(heir === NULL_ADDRESS); + }); + + it('heir can claim ownership only if owner is dead and timeout was reached', async function () { + const heir = accounts[1]; + await heritable.setHeir(heir, { from: owner }); + await expectThrow(heritable.claimHeirOwnership({ from: heir })); + + await heritable.proclaimDeath({ from: heir }); + await increaseTime(1); + await expectThrow(heritable.claimHeirOwnership({ from: heir })); + + await increaseTime(4141); + await heritable.claimHeirOwnership({ from: heir }); + assert.isTrue(await heritable.heir() === heir); + }); + + it('heir can\'t claim ownership if owner heartbeats', async function () { + const heir = accounts[1]; + await heritable.setHeir(heir, { from: owner }); + + await heritable.proclaimDeath({ from: heir }); + await heritable.heartbeat({ from: owner }); + await expectThrow(heritable.claimHeirOwnership({ from: heir })); + + await heritable.proclaimDeath({ from: heir }); + await increaseTime(4141); + await heritable.heartbeat({ from: owner }); + await expectThrow(heritable.claimHeirOwnership({ from: heir })); + }); + + it('should log events appropriately', async function () { + const heir = accounts[1]; + + const setHeirLogs = (await heritable.setHeir(heir, { from: owner })).logs; + const setHeirEvent = setHeirLogs.find(e => e.event === 'HeirChanged'); + + assert.isTrue(setHeirEvent.args.owner === owner); + assert.isTrue(setHeirEvent.args.newHeir === heir); + + const heartbeatLogs = (await heritable.heartbeat({ from: owner })).logs; + const heartbeatEvent = heartbeatLogs.find(e => e.event === 'OwnerHeartbeated'); + + assert.isTrue(heartbeatEvent.args.owner === owner); + + const proclaimDeathLogs = (await heritable.proclaimDeath({ from: heir })).logs; + const ownerDeadEvent = proclaimDeathLogs.find(e => e.event === 'OwnerProclaimedDead'); + + assert.isTrue(ownerDeadEvent.args.owner === owner); + assert.isTrue(ownerDeadEvent.args.heir === heir); + + await increaseTime(4141); + const claimHeirOwnershipLogs = (await heritable.claimHeirOwnership({ from: heir })).logs; + const ownershipTransferredEvent = claimHeirOwnershipLogs.find(e => e.event === 'OwnershipTransferred'); + const heirOwnershipClaimedEvent = claimHeirOwnershipLogs.find(e => e.event === 'HeirOwnershipClaimed'); + + assert.isTrue(ownershipTransferredEvent.args.previousOwner === owner); + assert.isTrue(ownershipTransferredEvent.args.newOwner === heir); + assert.isTrue(heirOwnershipClaimedEvent.args.previousOwner === owner); + assert.isTrue(heirOwnershipClaimedEvent.args.newOwner === heir); + }); +}); diff --git a/test/SimpleSavingsWallet.js b/test/SimpleSavingsWallet.js deleted file mode 100644 index 8ae34d58505..00000000000 --- a/test/SimpleSavingsWallet.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict' -import expectThrow from './helpers/expectThrow'; - -const SimpleSavingsWallet = artifacts.require('../contracts/examples/SimpleSavingsWallet.sol') - -contract('SimpleSavingsWallet', function(accounts) { - let savingsWallet - let owner - - const paymentAmount = 4242 - - beforeEach(async function() { - savingsWallet = await SimpleSavingsWallet.new(4141) - owner = await savingsWallet.owner() - }) - - it('should receive funds', async function() { - await web3.eth.sendTransaction({from: owner, to: savingsWallet.address, value: paymentAmount}) - assert.isTrue( - (new web3.BigNumber(paymentAmount)).equals(web3.eth.getBalance(savingsWallet.address)) - ) - }) - - it('owner can send funds', async function() { - // Receive payment so we have some money to spend. - await web3.eth.sendTransaction({from: accounts[9], to: savingsWallet.address, value: 1000000}) - await expectThrow(savingsWallet.sendTo(0, paymentAmount, {from: owner})) - await expectThrow(savingsWallet.sendTo(savingsWallet.address, paymentAmount, {from: owner})) - await expectThrow(savingsWallet.sendTo(accounts[1], 0, {from: owner})) - - const balance = web3.eth.getBalance(accounts[1]) - await savingsWallet.sendTo(accounts[1], paymentAmount, {from: owner}) - assert.isTrue(balance.plus(paymentAmount).equals(web3.eth.getBalance(accounts[1]))) - }) -}) diff --git a/test/SimpleSavingsWallet.test.js b/test/SimpleSavingsWallet.test.js new file mode 100644 index 00000000000..007363a18ae --- /dev/null +++ b/test/SimpleSavingsWallet.test.js @@ -0,0 +1,33 @@ + +import expectThrow from './helpers/expectThrow'; + +const SimpleSavingsWallet = artifacts.require('../contracts/examples/SimpleSavingsWallet.sol'); + +contract('SimpleSavingsWallet', function (accounts) { + let savingsWallet; + let owner; + + const paymentAmount = 4242; + + beforeEach(async function () { + savingsWallet = await SimpleSavingsWallet.new(4141); + owner = await savingsWallet.owner(); + }); + + it('should receive funds', async function () { + await web3.eth.sendTransaction({ from: owner, to: savingsWallet.address, value: paymentAmount }); + assert.isTrue((new web3.BigNumber(paymentAmount)).equals(web3.eth.getBalance(savingsWallet.address))); + }); + + it('owner can send funds', async function () { + // Receive payment so we have some money to spend. + await web3.eth.sendTransaction({ from: accounts[9], to: savingsWallet.address, value: 1000000 }); + await expectThrow(savingsWallet.sendTo(0, paymentAmount, { from: owner })); + await expectThrow(savingsWallet.sendTo(savingsWallet.address, paymentAmount, { from: owner })); + await expectThrow(savingsWallet.sendTo(accounts[1], 0, { from: owner })); + + const balance = web3.eth.getBalance(accounts[1]); + await savingsWallet.sendTo(accounts[1], paymentAmount, { from: owner }); + assert.isTrue(balance.plus(paymentAmount).equals(web3.eth.getBalance(accounts[1]))); + }); +}); From 22c1403e5bf19d54fc92988a37087e050f259842 Mon Sep 17 00:00:00 2001 From: Alejandro Santander Date: Tue, 16 Jan 2018 13:58:19 -0300 Subject: [PATCH 21/23] Fix solidity style issue --- contracts/ownership/Heritable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ownership/Heritable.sol b/contracts/ownership/Heritable.sol index 11aa22f8cb5..647fb1f0a81 100644 --- a/contracts/ownership/Heritable.sol +++ b/contracts/ownership/Heritable.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.11; -import './Ownable.sol'; +import "./Ownable.sol"; /** From 9d005b492d25152af4f2da05c861dbd66ac57533 Mon Sep 17 00:00:00 2001 From: Alejandro Santander Date: Tue, 16 Jan 2018 15:47:17 -0300 Subject: [PATCH 22/23] Explicit uint size --- contracts/ownership/Heritable.sol | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/ownership/Heritable.sol b/contracts/ownership/Heritable.sol index 647fb1f0a81..de57fec6383 100644 --- a/contracts/ownership/Heritable.sol +++ b/contracts/ownership/Heritable.sol @@ -14,15 +14,14 @@ contract Heritable is Ownable { address public heir; // Time window the owner has to notify they are alive. - uint public heartbeatTimeout; + uint256 public heartbeatTimeout; // Timestamp of the owner's death, as pronounced by the heir. - uint public timeOfDeath; - + uint256 public timeOfDeath; event HeirChanged(address indexed owner, address indexed newHeir); event OwnerHeartbeated(address indexed owner); - event OwnerProclaimedDead(address indexed owner, address indexed heir, uint timeOfDeath); + event OwnerProclaimedDead(address indexed owner, address indexed heir, uint256 timeOfDeath); event HeirOwnershipClaimed(address indexed previousOwner, address indexed newOwner); @@ -40,7 +39,7 @@ contract Heritable is Ownable { * @param _heartbeatTimeout time available for the owner to notify they are alive, * before the heir can take ownership. */ - function Heritable(uint _heartbeatTimeout) public { + function Heritable(uint256 _heartbeatTimeout) public { setHeartbeatTimeout(_heartbeatTimeout); } @@ -89,7 +88,7 @@ contract Heritable is Ownable { timeOfDeath = 0; } - function setHeartbeatTimeout(uint newHeartbeatTimeout) internal onlyOwner { + function setHeartbeatTimeout(uint256 newHeartbeatTimeout) internal onlyOwner { require(ownerLives()); heartbeatTimeout = newHeartbeatTimeout; } From c01203b0b727b53f725c9ff5105fd680b98e2804 Mon Sep 17 00:00:00 2001 From: Alejandro Santander Date: Thu, 18 Jan 2018 16:58:45 -0300 Subject: [PATCH 23/23] Index addreses in events and explicit uint size --- contracts/examples/SimpleSavingsWallet.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/examples/SimpleSavingsWallet.sol b/contracts/examples/SimpleSavingsWallet.sol index e6d11d5a208..80a70af19d0 100644 --- a/contracts/examples/SimpleSavingsWallet.sol +++ b/contracts/examples/SimpleSavingsWallet.sol @@ -15,11 +15,11 @@ import "../ownership/Heritable.sol"; */ contract SimpleSavingsWallet is Heritable { - event Sent(address payee, uint amount, uint balance); - event Received(address payer, uint amount, uint balance); + event Sent(address indexed payee, uint256 amount, uint256 balance); + event Received(address indexed payer, uint256 amount, uint256 balance); - function SimpleSavingsWallet(uint _heartbeatTimeout) Heritable(_heartbeatTimeout) public {} + function SimpleSavingsWallet(uint256 _heartbeatTimeout) Heritable(_heartbeatTimeout) public {} /** * @dev wallet can receive funds. @@ -31,7 +31,7 @@ contract SimpleSavingsWallet is Heritable { /** * @dev wallet can send funds */ - function sendTo(address payee, uint amount) public onlyOwner { + function sendTo(address payee, uint256 amount) public onlyOwner { require(payee != 0 && payee != address(this)); require(amount > 0); payee.transfer(amount);