Skip to content

Add timelocked transfer to SafeOwnable #305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Apr 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
543a3c2
move SafeOwnable to subdirectory
ItsNickBarry Apr 5, 2025
364e29b
add timelock to SafeOwnable ownership transfer
ItsNickBarry Apr 5, 2025
3af729c
add Timestamp type with free comparator functions
ItsNickBarry Apr 5, 2025
3d7e639
add != Timestamp operator
ItsNickBarry Apr 5, 2025
ce16c31
add safety note
ItsNickBarry Apr 5, 2025
2df13dc
add Block library with typed timestamp function
ItsNickBarry Apr 5, 2025
2f31354
add Duration type with free comparator functions
ItsNickBarry Apr 5, 2025
60c4faa
add + and - Duration operators
ItsNickBarry Apr 5, 2025
083f969
fix Duration variable names
ItsNickBarry Apr 5, 2025
37bbce1
add Timestamp-Duration add and sub functions
ItsNickBarry Apr 6, 2025
0b544e1
move add and sub timestamp functions to Timestamp library
ItsNickBarry Apr 6, 2025
0e3486b
downcase duration type name, add Duration library
ItsNickBarry Apr 6, 2025
e0f2ec5
add Duration and Timestamp to barrel file
ItsNickBarry Apr 6, 2025
666488d
add Duration static factory functions
ItsNickBarry Apr 6, 2025
b18d283
test Duration and Timestamp
ItsNickBarry Apr 6, 2025
01e6bc5
add fromUint256 static function to Timestamp
ItsNickBarry Apr 6, 2025
20356eb
test Block#timestamp function
ItsNickBarry Apr 6, 2025
a1b6c08
Merge branch 'time-types' into safe-ownable-timelock
ItsNickBarry Apr 6, 2025
593f921
add timelock type based on uint96
ItsNickBarry Apr 6, 2025
dbcb4b0
Merge branch 'timelock' into safe-ownable-timelock
ItsNickBarry Apr 6, 2025
aefb8a0
use bytes12 as underlying type of timelock
ItsNickBarry Apr 6, 2025
c5c6587
add Timelock to barrel file
ItsNickBarry Apr 6, 2025
d00d672
fix timelock factory functions
ItsNickBarry Apr 6, 2025
ed22096
fix Timelock timestamp getters
ItsNickBarry Apr 6, 2025
8f973b3
panic if end timestamp is less than start timestamp
ItsNickBarry Apr 6, 2025
0baac38
test Timelock
ItsNickBarry Apr 6, 2025
4f32839
Merge pull request #309 from solidstate-network/timelock
ItsNickBarry Apr 6, 2025
ddc163a
Merge branch 'time-types' into safe-ownable-timelock
ItsNickBarry Apr 6, 2025
c940bda
move time types to utils/time/ directory
ItsNickBarry Apr 6, 2025
bda4a43
Merge branch 'time-types' into safe-ownable-timelock
ItsNickBarry Apr 6, 2025
537244b
use timelock type for SafeOwnable
ItsNickBarry Apr 6, 2025
f80902d
test SafeOwnable transfer timelock
ItsNickBarry Apr 6, 2025
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
9 changes: 0 additions & 9 deletions contracts/access/ownable/_ISafeOwnable.sol

This file was deleted.

53 changes: 0 additions & 53 deletions contracts/access/ownable/_SafeOwnable.sol

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.8.24;

import { IOwnable } from './IOwnable.sol';
import { IOwnable } from '../IOwnable.sol';
import { _ISafeOwnable } from './_ISafeOwnable.sol';

interface ISafeOwnable is _ISafeOwnable, IOwnable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

pragma solidity ^0.8.24;

import { Ownable } from './Ownable.sol';
import { Ownable } from '../Ownable.sol';
import { _Ownable } from '../_Ownable.sol';
import { ISafeOwnable } from './ISafeOwnable.sol';
import { _Ownable } from './_Ownable.sol';
import { _SafeOwnable } from './_SafeOwnable.sol';

/**
Expand Down
16 changes: 16 additions & 0 deletions contracts/access/ownable/safe/_ISafeOwnable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import { timestamp } from '../../../utils/time/Timestamp.sol';
import { _IOwnable } from '../_IOwnable.sol';

interface _ISafeOwnable is _IOwnable {
event OwnershipTransferInitiated(
address indexed previousOwner,
address indexed newOwner,
timestamp timelockEndTimestamp
);

error SafeOwnable__NotNomineeOwner();
}
108 changes: 108 additions & 0 deletions contracts/access/ownable/safe/_SafeOwnable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import { ERC173Storage } from '../../../storage/ERC173Storage.sol';
import { Duration, duration } from '../../../utils/time/Duration.sol';
import { Timelock, timelock } from '../../../utils/time/Timelock.sol';
import { _Ownable } from '../_Ownable.sol';
import { _ISafeOwnable } from './_ISafeOwnable.sol';

abstract contract _SafeOwnable is _ISafeOwnable, _Ownable {
modifier onlyNomineeOwner() {
if (_msgSender() != _nomineeOwner())
revert SafeOwnable__NotNomineeOwner();
_;
}

/**
* @notice get the nominated owner who has permission to call acceptOwnership
*/
function _nomineeOwner() internal view virtual returns (address) {
return
ERC173Storage
.layout(ERC173Storage.DEFAULT_STORAGE_SLOT)
.nomineeOwner;
}

/**
* @notice query the waiting period after an ownership transfer is initiated before the nomineeOwner can claim ownership
* @return transferTimelockDuration duration of waiting period
*/
function _getTransferTimelockDuration()
internal
view
virtual
returns (duration transferTimelockDuration)
{
transferTimelockDuration = ERC173Storage
.layout(ERC173Storage.DEFAULT_STORAGE_SLOT)
.transferTimelockDuration;
}

/**
* @notice accept transfer of contract ownership
*/
function _acceptOwnership() internal virtual onlyNomineeOwner {
ERC173Storage.Layout storage $ = ERC173Storage.layout(
ERC173Storage.DEFAULT_STORAGE_SLOT
);

$.transferTimelock.requireUnlocked();

_setOwner(_msgSender());

delete $.nomineeOwner;
}

/**
* @notice grant permission to given address to claim contract ownership
*/
function _transferOwnership(
address account
) internal virtual override onlyOwner {
_setNomineeOwner(account);
timelock transferTimelock = _setTransferTimelock();
emit OwnershipTransferInitiated(
_owner(),
account,
transferTimelock.getEndTimestamp()
);
}

/**
* @notice set nominee owner
*/
function _setNomineeOwner(address account) internal virtual {
ERC173Storage
.layout(ERC173Storage.DEFAULT_STORAGE_SLOT)
.nomineeOwner = account;
}

/**
* @notice set the transfer timelock relative to current timestamp
*/
function _setTransferTimelock()
internal
virtual
returns (timelock transferTimelock)
{
transferTimelock = Timelock.create(_getTransferTimelockDuration());

ERC173Storage
.layout(ERC173Storage.DEFAULT_STORAGE_SLOT)
.transferTimelock = transferTimelock;
}

/**
* @notice set the duration used when creating transfer timelocks
* @param transferTimelockDuration duration of transfer timelock
*/
function _setTransferTimelockDuration(
duration transferTimelockDuration
) internal virtual {
ERC173Storage
.layout(ERC173Storage.DEFAULT_STORAGE_SLOT)
.transferTimelockDuration = transferTimelockDuration;
}
}
12 changes: 8 additions & 4 deletions contracts/index.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import './access/initializable/Initializable.sol';
import './access/initializable/_IInitializable.sol';
import './access/initializable/_Initializable.sol';
import './access/ownable/IOwnable.sol';
import './access/ownable/ISafeOwnable.sol';
import './access/ownable/Ownable.sol';
import './access/ownable/SafeOwnable.sol';
import './access/ownable/_IOwnable.sol';
import './access/ownable/_ISafeOwnable.sol';
import './access/ownable/_Ownable.sol';
import './access/ownable/_SafeOwnable.sol';
import './access/ownable/safe/ISafeOwnable.sol';
import './access/ownable/safe/SafeOwnable.sol';
import './access/ownable/safe/_ISafeOwnable.sol';
import './access/ownable/safe/_SafeOwnable.sol';
import './access/partially_pausable/IPartiallyPausable.sol';
import './access/partially_pausable/PartiallyPausable.sol';
import './access/partially_pausable/_IPartiallyPausable.sol';
Expand Down Expand Up @@ -245,6 +245,7 @@ import './token/non_fungible/metadata/_INonFungibleTokenMetadata.sol';
import './token/non_fungible/metadata/_NonFungibleTokenMetadata.sol';
import './utils/Address.sol';
import './utils/Array.sol';
import './utils/Block.sol';
import './utils/Bool.sol';
import './utils/Bytes32.sol';
import './utils/IMulticall.sol';
Expand All @@ -257,3 +258,6 @@ import './utils/SafeERC20.sol';
import './utils/Uint256.sol';
import './utils/_IMulticall.sol';
import './utils/_Multicall.sol';
import './utils/time/Duration.sol';
import './utils/time/Timelock.sol';
import './utils/time/Timestamp.sol';
4 changes: 4 additions & 0 deletions contracts/storage/ERC173Storage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
pragma solidity ^0.8.24;

import { Slot } from '../data/Slot.sol';
import { duration } from '../utils/time/Duration.sol';
import { timelock } from '../utils/time/Timelock.sol';

library ERC173Storage {
/**
Expand All @@ -11,6 +13,8 @@ library ERC173Storage {
struct Layout {
address owner;
address nomineeOwner;
timelock transferTimelock;
duration transferTimelockDuration;
}

Slot.StorageSlot internal constant DEFAULT_STORAGE_SLOT =
Expand Down
48 changes: 48 additions & 0 deletions contracts/test/DurationTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import { duration } from '../utils/time/Duration.sol';

contract DurationTest {
function eq(duration d0, duration d1) external pure returns (bool status) {
status = d0 == d1;
}

function notEq(
duration d0,
duration d1
) external pure returns (bool status) {
status = d0 != d1;
}

function gt(duration d0, duration d1) external pure returns (bool status) {
status = d0 > d1;
}

function lt(duration d0, duration d1) external pure returns (bool status) {
status = d0 < d1;
}

function gte(duration d0, duration d1) external pure returns (bool status) {
status = d0 >= d1;
}

function lte(duration d0, duration d1) external pure returns (bool status) {
status = d0 <= d1;
}

function add(
duration d0,
duration d1
) external pure returns (duration result) {
result = d0 + d1;
}

function sub(
duration d0,
duration d1
) external pure returns (duration result) {
result = d0 - d1;
}
}
49 changes: 49 additions & 0 deletions contracts/test/TimestampTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import { timestamp } from '../utils/time/Timestamp.sol';

contract TimestampTest {
function eq(
timestamp t0,
timestamp t1
) external pure returns (bool status) {
status = t0 == t1;
}

function notEq(
timestamp t0,
timestamp t1
) external pure returns (bool status) {
status = t0 != t1;
}

function gt(
timestamp t0,
timestamp t1
) external pure returns (bool status) {
status = t0 > t1;
}

function lt(
timestamp t0,
timestamp t1
) external pure returns (bool status) {
status = t0 < t1;
}

function gte(
timestamp t0,
timestamp t1
) external pure returns (bool status) {
status = t0 >= t1;
}

function lte(
timestamp t0,
timestamp t1
) external pure returns (bool status) {
status = t0 <= t1;
}
}
17 changes: 17 additions & 0 deletions contracts/utils/Block.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import { timestamp as _timestamp } from './time/Timestamp.sol';

library Block {
/**
* @notice replacement for block.timestamp which returns the current time as a Timestamp type
* @return blockTimestamp current timestamp
*/
function timestamp() internal view returns (_timestamp blockTimestamp) {
assembly {
blockTimestamp := timestamp()
}
}
}
Loading