Unverified Commit 0dc71173 by Francisco Giordano Committed by GitHub

Remove Claimable, DelayedClaimable, Heritable (#1274)

* remove Claimable, DelayedClaimable, Heritable

* remove SimpleSavingsWallet example which used Heritable
parent 00abd3aa
pragma solidity ^0.4.24;
import "../ownership/Heritable.sol";
/**
* @title SimpleSavingsWallet
* @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 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 Heritable {
event Sent(address indexed payee, uint256 amount, uint256 balance);
event Received(address indexed payer, uint256 amount, uint256 balance);
constructor(uint256 _heartbeatTimeout) Heritable(_heartbeatTimeout) public {}
/**
* @dev wallet can receive funds.
*/
function () external payable {
emit Received(msg.sender, msg.value, address(this).balance);
}
/**
* @dev wallet can send funds
*/
function sendTo(address _payee, uint256 _amount) public onlyOwner {
require(_payee != address(0) && _payee != address(this));
require(_amount > 0);
_payee.transfer(_amount);
emit Sent(_payee, _amount, address(this).balance);
}
}
pragma solidity ^0.4.24;
import "./Ownable.sol";
/**
* @title Claimable
* @dev Extension for the Ownable contract, where the ownership needs to be claimed.
* This allows the new owner to accept the transfer.
*/
contract Claimable is Ownable {
address public pendingOwner;
/**
* @dev Modifier throws if called by any account other than the pendingOwner.
*/
modifier onlyPendingOwner() {
require(msg.sender == pendingOwner);
_;
}
/**
* @dev Allows the current owner to set the pendingOwner address.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
pendingOwner = newOwner;
}
/**
* @dev Allows the pendingOwner address to finalize the transfer.
*/
function claimOwnership() public onlyPendingOwner {
emit OwnershipTransferred(owner, pendingOwner);
owner = pendingOwner;
pendingOwner = address(0);
}
}
pragma solidity ^0.4.24;
import "./Claimable.sol";
/**
* @title DelayedClaimable
* @dev Extension for the Claimable contract, where the ownership needs to be claimed before/after
* a certain block number.
*/
contract DelayedClaimable is Claimable {
uint256 public end;
uint256 public start;
/**
* @dev Used to specify the time period during which a pending
* owner can claim ownership.
* @param _start The earliest time ownership can be claimed.
* @param _end The latest time ownership can be claimed.
*/
function setLimits(uint256 _start, uint256 _end) public onlyOwner {
require(_start <= _end);
end = _end;
start = _start;
}
/**
* @dev Allows the pendingOwner address to finalize the transfer, as long as it is called within
* the specified start and end time.
*/
function claimOwnership() public onlyPendingOwner {
require((block.number <= end) && (block.number >= start));
emit OwnershipTransferred(owner, pendingOwner);
owner = pendingOwner;
pendingOwner = address(0);
end = 0;
}
}
pragma solidity ^0.4.24;
import "./Ownable.sol";
/**
* @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 Heritable is Ownable {
address private heir_;
// Time window the owner has to notify they are alive.
uint256 private heartbeatTimeout_;
// Timestamp of the owner's death, as pronounced by the heir.
uint256 private timeOfDeath_;
event HeirChanged(address indexed owner, address indexed newHeir);
event OwnerHeartbeated(address indexed owner);
event OwnerProclaimedDead(
address indexed owner,
address indexed heir,
uint256 timeOfDeath
);
event HeirOwnershipClaimed(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev Throw an exception if called by any account other than the heir's.
*/
modifier onlyHeir() {
require(msg.sender == heir_);
_;
}
/**
* @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.
*/
constructor(uint256 _heartbeatTimeout) public {
heartbeatTimeout_ = _heartbeatTimeout;
}
function setHeir(address _newHeir) public onlyOwner {
require(_newHeir != owner);
heartbeat();
emit HeirChanged(owner, _newHeir);
heir_ = _newHeir;
}
/**
* @dev Use these getter functions to access the internal variables in
* an inherited contract.
*/
function heir() public view returns(address) {
return heir_;
}
function heartbeatTimeout() public view returns(uint256) {
return heartbeatTimeout_;
}
function timeOfDeath() public view returns(uint256) {
return timeOfDeath_;
}
/**
* @dev set heir = 0x0
*/
function removeHeir() public onlyOwner {
heartbeat();
heir_ = address(0);
}
/**
* @dev Heir can pronounce the owners death. To claim the ownership, they will
* have to wait for `heartbeatTimeout` seconds.
*/
function proclaimDeath() public onlyHeir {
require(_ownerLives());
emit OwnerProclaimedDead(owner, heir_, timeOfDeath_);
// solium-disable-next-line security/no-block-members
timeOfDeath_ = block.timestamp;
}
/**
* @dev Owner can send a heartbeat if they were mistakenly pronounced dead.
*/
function heartbeat() public onlyOwner {
emit OwnerHeartbeated(owner);
timeOfDeath_ = 0;
}
/**
* @dev Allows heir to transfer ownership only if heartbeat has timed out.
*/
function claimHeirOwnership() public onlyHeir {
require(!_ownerLives());
// solium-disable-next-line security/no-block-members
require(block.timestamp >= timeOfDeath_ + heartbeatTimeout_);
emit OwnershipTransferred(owner, heir_);
emit HeirOwnershipClaimed(owner, heir_);
owner = heir_;
timeOfDeath_ = 0;
}
function _ownerLives() internal view returns (bool) {
return timeOfDeath_ == 0;
}
}
const { increaseTime } = require('./helpers/increaseTime');
const { expectThrow } = require('./helpers/expectThrow');
const { assertRevert } = require('./helpers/assertRevert');
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
const Heritable = artifacts.require('Heritable');
const BigNumber = web3.BigNumber;
require('chai')
.use(require('chai-bignumber')(BigNumber))
.should();
contract('Heritable', function ([_, owner, heir, anyone]) {
const heartbeatTimeout = 4141;
let heritable;
beforeEach(async function () {
heritable = await Heritable.new(heartbeatTimeout, { from: owner });
});
it('should start off with an owner, but without heir', async function () {
const heir = await heritable.heir();
owner.should.be.a('string').that.is.not.equal(NULL_ADDRESS);
heir.should.be.a('string').that.is.equal(NULL_ADDRESS);
});
it('only owner should set heir', async function () {
await heritable.setHeir(heir, { from: owner });
await expectThrow(heritable.setHeir(heir, { from: anyone }));
});
it('owner can\'t be heir', async function () {
await assertRevert(heritable.setHeir(owner, { from: owner }));
});
it('owner can remove heir', async function () {
await heritable.setHeir(heir, { from: owner });
(await heritable.heir()).should.equal(heir);
await heritable.removeHeir({ from: owner });
(await heritable.heir()).should.equal(NULL_ADDRESS);
});
it('heir can claim ownership only if owner is dead and timeout was reached', async function () {
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(heartbeatTimeout);
await heritable.claimHeirOwnership({ from: heir });
(await heritable.heir()).should.equal(heir);
});
it('only heir can proclaim death', async function () {
await assertRevert(heritable.proclaimDeath({ from: owner }));
await assertRevert(heritable.proclaimDeath({ from: anyone }));
});
it('heir can\'t proclaim death if owner is death', async function () {
await heritable.setHeir(heir, { from: owner });
await heritable.proclaimDeath({ from: heir });
await assertRevert(heritable.proclaimDeath({ from: heir }));
});
it('heir can\'t claim ownership if owner heartbeats', async function () {
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(heartbeatTimeout);
await heritable.heartbeat({ from: owner });
await expectThrow(heritable.claimHeirOwnership({ from: heir }));
});
it('should log events appropriately', async function () {
const setHeirLogs = (await heritable.setHeir(heir, { from: owner })).logs;
const setHeirEvent = setHeirLogs.find(e => e.event === 'HeirChanged');
setHeirEvent.args.owner.should.equal(owner);
setHeirEvent.args.newHeir.should.equal(heir);
const heartbeatLogs = (await heritable.heartbeat({ from: owner })).logs;
const heartbeatEvent = heartbeatLogs.find(e => e.event === 'OwnerHeartbeated');
heartbeatEvent.args.owner.should.equal(owner);
const proclaimDeathLogs = (await heritable.proclaimDeath({ from: heir })).logs;
const ownerDeadEvent = proclaimDeathLogs.find(e => e.event === 'OwnerProclaimedDead');
ownerDeadEvent.args.owner.should.equal(owner);
ownerDeadEvent.args.heir.should.equal(heir);
await increaseTime(heartbeatTimeout);
const claimHeirOwnershipLogs = (await heritable.claimHeirOwnership({ from: heir })).logs;
const ownershipTransferredEvent = claimHeirOwnershipLogs.find(e => e.event === 'OwnershipTransferred');
const heirOwnershipClaimedEvent = claimHeirOwnershipLogs.find(e => e.event === 'HeirOwnershipClaimed');
ownershipTransferredEvent.args.previousOwner.should.equal(owner);
ownershipTransferredEvent.args.newOwner.should.equal(heir);
heirOwnershipClaimedEvent.args.previousOwner.should.equal(owner);
heirOwnershipClaimedEvent.args.newOwner.should.equal(heir);
});
it('timeOfDeath can be queried', async function () {
(await heritable.timeOfDeath()).should.be.bignumber.equal(0);
});
it('heartbeatTimeout can be queried', async function () {
(await heritable.heartbeatTimeout()).should.be.bignumber.equal(heartbeatTimeout);
});
});
const { expectThrow } = require('./helpers/expectThrow');
const { ethGetBalance, ethSendTransaction } = require('./helpers/web3');
const SimpleSavingsWallet = artifacts.require('SimpleSavingsWallet');
const BigNumber = web3.BigNumber;
require('chai')
.use(require('chai-bignumber')(BigNumber))
.should();
contract('SimpleSavingsWallet', function ([_, owner, anyone]) {
let savingsWallet;
const paymentAmount = 4242;
beforeEach(async function () {
savingsWallet = await SimpleSavingsWallet.new(4141, { from: owner });
});
it('should receive funds', async function () {
await ethSendTransaction({ from: owner, to: savingsWallet.address, value: paymentAmount });
const balance = await ethGetBalance(savingsWallet.address);
balance.should.be.bignumber.equal(paymentAmount);
});
it('owner can send funds', async function () {
// Receive payment so we have some money to spend.
await ethSendTransaction({ from: anyone, 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(anyone, 0, { from: owner }));
const balance = await ethGetBalance(anyone);
await savingsWallet.sendTo(anyone, paymentAmount, { from: owner });
const updatedBalance = await ethGetBalance(anyone);
balance.plus(paymentAmount).should.be.bignumber.equal(updatedBalance);
});
});
const { assertRevert } = require('../helpers/assertRevert');
const Claimable = artifacts.require('Claimable');
const BigNumber = web3.BigNumber;
require('chai')
.use(require('chai-bignumber')(BigNumber))
.should();
contract('Claimable', function ([_, owner, newOwner, anyone]) {
let claimable;
beforeEach(async function () {
claimable = await Claimable.new({ from: owner });
});
it('should have an owner', async function () {
(await claimable.owner()).should.not.equal(0);
});
it('changes pendingOwner after transfer', async function () {
await claimable.transferOwnership(newOwner, { from: owner });
(await claimable.pendingOwner()).should.equal(newOwner);
});
it('should prevent to claimOwnership from anyone', async function () {
await assertRevert(claimable.claimOwnership({ from: anyone }));
});
it('should prevent non-owners from transfering', async function () {
await assertRevert(claimable.transferOwnership(anyone, { from: anyone }));
});
describe('after initiating a transfer', function () {
beforeEach(async function () {
await claimable.transferOwnership(newOwner, { from: owner });
});
it('changes allow pending owner to claim ownership', async function () {
await claimable.claimOwnership({ from: newOwner });
(await claimable.owner()).should.equal(newOwner);
});
});
});
const { assertRevert } = require('../helpers/assertRevert');
const BigNumber = web3.BigNumber;
require('chai')
.use(require('chai-bignumber')(BigNumber))
.should();
const DelayedClaimable = artifacts.require('DelayedClaimable');
contract('DelayedClaimable', function ([_, owner, newOwner]) {
beforeEach(async function () {
this.delayedClaimable = await DelayedClaimable.new({ from: owner });
});
it('can set claim blocks', async function () {
await this.delayedClaimable.transferOwnership(newOwner, { from: owner });
await this.delayedClaimable.setLimits(0, 1000, { from: owner });
(await this.delayedClaimable.end()).should.be.bignumber.equal(1000);
(await this.delayedClaimable.start()).should.be.bignumber.equal(0);
});
it('changes pendingOwner after transfer successful', async function () {
await this.delayedClaimable.transferOwnership(newOwner, { from: owner });
await this.delayedClaimable.setLimits(0, 1000, { from: owner });
(await this.delayedClaimable.end()).should.be.bignumber.equal(1000);
(await this.delayedClaimable.start()).should.be.bignumber.equal(0);
(await this.delayedClaimable.pendingOwner()).should.equal(newOwner);
await this.delayedClaimable.claimOwnership({ from: newOwner });
(await this.delayedClaimable.owner()).should.equal(newOwner);
});
it('changes pendingOwner after transfer fails', async function () {
await this.delayedClaimable.transferOwnership(newOwner, { from: owner });
await this.delayedClaimable.setLimits(100, 110, { from: owner });
(await this.delayedClaimable.end()).should.be.bignumber.equal(110);
(await this.delayedClaimable.start()).should.be.bignumber.equal(100);
(await this.delayedClaimable.pendingOwner()).should.equal(newOwner);
await assertRevert(this.delayedClaimable.claimOwnership({ from: newOwner }));
(await this.delayedClaimable.owner()).should.not.equal(newOwner);
});
it('set end and start invalid values fail', async function () {
await this.delayedClaimable.transferOwnership(newOwner, { from: owner });
await assertRevert(this.delayedClaimable.setLimits(1001, 1000, { from: owner }));
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment