-
Notifications
You must be signed in to change notification settings - Fork 12k
Add Ownable2StepSign, an extension of Ownable2Step #5628
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
Open
markolazic01
wants to merge
6
commits into
OpenZeppelin:master
Choose a base branch
from
markolazic01:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
1af473d
Add Ownable2StepSign
markolazic01 2da4cb4
Add domain separator test for Ownable2StepSign contract
markolazic01 0fbfe47
Apply variable name change to all places
markolazic01 e9b000d
Remove the DOMAIN_SEPARATOR domain discovery method
markolazic01 9f598cc
Adapt to EIP1271, switch from Ownable2Step to Ownable
markolazic01 cc3b139
Update OwnableRecipientSignOff.sol
Amxx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'openzeppelin-solidity': major | ||
--- | ||
|
||
`Ownable2StepSign`: Introduce Ownable2Step extension which allows for implicit ownership acceptance via signature. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {Ownable} from "./Ownable.sol"; | ||
import {SignatureChecker} from "../utils/cryptography/SignatureChecker.sol"; | ||
import {EIP712} from "../utils/cryptography/EIP712.sol"; | ||
|
||
/** | ||
* @dev Contract module which provides a basic access control mechanism, where | ||
* there is an account (an owner) that can be granted exclusive access to | ||
* specific functions. | ||
* | ||
* This extension of the {Ownable} includes a signature protected ownership transfer mechanism, | ||
* where the `newOwner` must provide a signature in order to replace the old one. | ||
* This can help prevent common mistakes, such as transfers of ownership to incorrect accounts, | ||
* and improve the safety of a `newOwner` wallet, as it can stay offline at a time. | ||
* Function `transferOwnership` provides a safe way of making an ownership transfer to contracts as well. | ||
* | ||
* The initial owner is specified at deployment time in the constructor for `Ownable`. This | ||
* can later be changed with {transferOwnership}. | ||
* | ||
* This module is used through inheritance. It will make available all functions | ||
* from parent (Ownable). | ||
*/ | ||
abstract contract OwnableRecipientSignOff is Ownable, EIP712 { | ||
/** | ||
* @dev Nonce used for signatures. | ||
*/ | ||
uint256 private _nonce; | ||
|
||
bytes32 private constant TRANSFER_OWNERSHIP_TYPEHASH = | ||
keccak256("TransferOwnership(uint256 nonce,uint256 deadline)"); | ||
|
||
/** | ||
* @dev Permit deadline has expired. | ||
*/ | ||
error ExpiredSignature(uint256 deadline); | ||
|
||
/** | ||
* @dev Mismatched signature. | ||
*/ | ||
error InvalidSigner(); | ||
|
||
constructor(string memory name) EIP712(name, "1") {} | ||
|
||
/** | ||
* @dev Transfers ownership of the contract to a new account (`newOwner`). | ||
* Can only be called by the current owner. | ||
* Requires a `newOwner` account's signature. | ||
*/ | ||
function transferOwnership(address newOwner, uint256 deadline, bytes memory signature) public virtual onlyOwner { | ||
if (block.timestamp > deadline) { | ||
revert ExpiredSignature(deadline); | ||
} | ||
if ( | ||
!SignatureChecker.isValidSignatureNow( | ||
newOwner, | ||
_hashTypedDataV4(keccak256(abi.encode(TRANSFER_OWNERSHIP_TYPEHASH, _useNonce(), deadline))), | ||
signature | ||
) | ||
) { | ||
revert InvalidSigner(); | ||
} | ||
super._transferOwnership(newOwner); | ||
} | ||
|
||
/** | ||
* @dev Consumes a nonce. | ||
* | ||
* Returns the current value and increments nonce. | ||
*/ | ||
function _useNonce() internal virtual returns (uint256) { | ||
// Flow guarantees that the nonce never overflows. | ||
unchecked { | ||
// It is important to do x++ and not ++x here. | ||
return _nonce++; | ||
} | ||
} | ||
|
||
/** | ||
* @dev Returns the next unused nonce. | ||
*/ | ||
function nonce() public view virtual returns (uint256) { | ||
return _nonce; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {Test} from "forge-std/Test.sol"; | ||
import {Vm} from "forge-std/Vm.sol"; | ||
|
||
import {OwnableRecipientSignOff} from "@openzeppelin/contracts/access/OwnableRecipientSignOff.sol"; | ||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; | ||
|
||
contract OwnableRecipientSignOffMock is OwnableRecipientSignOff { | ||
bytes32 private constant TRANSFER_OWNERSHIP_TYPEHASH = | ||
keccak256("TransferOwnership(uint256 nonce,uint256 deadline)"); | ||
|
||
constructor(address initialOwner) OwnableRecipientSignOff("OwnableRecipientSignOff") Ownable(initialOwner) {} | ||
|
||
function computeHash(uint256 nonce, uint256 deadline) external view returns (bytes32) { | ||
return _hashTypedDataV4(keccak256(abi.encode(TRANSFER_OWNERSHIP_TYPEHASH, nonce, deadline))); | ||
} | ||
} | ||
|
||
contract OwnableRecipientSignOffTest is Test { | ||
address private constant INITIAL_OWNER = address(1); | ||
OwnableRecipientSignOffMock private _ownableRecipientSignMock; | ||
Vm.Wallet private _newOwner; | ||
|
||
function setUp() external { | ||
_ownableRecipientSignMock = new OwnableRecipientSignOffMock(INITIAL_OWNER); | ||
_newOwner = vm.createWallet("_newOwner"); | ||
assertEq(_ownableRecipientSignMock.owner(), INITIAL_OWNER); | ||
} | ||
|
||
function testTransferOwnership() public { | ||
uint256 deadline = block.timestamp; | ||
uint256 nonce = 0; | ||
|
||
bytes32 hash = _ownableRecipientSignMock.computeHash(nonce, deadline); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_newOwner, hash); | ||
bytes memory signature = abi.encodePacked(r, s, v); | ||
|
||
vm.prank(INITIAL_OWNER); | ||
_ownableRecipientSignMock.transferOwnership(_newOwner.addr, deadline, signature); | ||
|
||
assertEq(_ownableRecipientSignMock.owner(), _newOwner.addr); | ||
assertEq(_ownableRecipientSignMock.nonce(), nonce + 1); | ||
} | ||
|
||
function testRevertWhenUnauthorizedCallIsMadeToTransferOwnership() external { | ||
uint256 deadline = block.timestamp; | ||
uint256 nonce = 0; | ||
address unauthorizedCaller = address(2); | ||
|
||
bytes32 hash = _ownableRecipientSignMock.computeHash(nonce, deadline); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_newOwner, hash); | ||
bytes memory signature = abi.encodePacked(r, s, v); | ||
|
||
vm.prank(unauthorizedCaller); | ||
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, unauthorizedCaller)); | ||
_ownableRecipientSignMock.transferOwnership(_newOwner.addr, deadline, signature); | ||
} | ||
|
||
function testRevertTransferOwnershipWhenSignatureExpires() external { | ||
uint256 deadline = block.timestamp - 1; | ||
uint256 nonce = 0; | ||
|
||
bytes32 hash = _ownableRecipientSignMock.computeHash(nonce, deadline); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_newOwner, hash); | ||
bytes memory signature = abi.encodePacked(r, s, v); | ||
|
||
vm.prank(INITIAL_OWNER); | ||
vm.expectRevert(abi.encodeWithSelector(OwnableRecipientSignOff.ExpiredSignature.selector, deadline)); | ||
_ownableRecipientSignMock.transferOwnership(_newOwner.addr, deadline, signature); | ||
} | ||
|
||
function testRevertTransferOwnershipWhenSignatureIsInvalid() external { | ||
Vm.Wallet memory falseSigner = vm.createWallet("falseSigner"); | ||
uint256 deadline = block.timestamp; | ||
uint256 nonce = 0; | ||
|
||
bytes32 hash = _ownableRecipientSignMock.computeHash(nonce, deadline); | ||
// Data is valid but signed by an inadequate wallet. | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(falseSigner, hash); | ||
bytes memory signature = abi.encodePacked(r, s, v); | ||
|
||
vm.prank(INITIAL_OWNER); | ||
vm.expectRevert(OwnableRecipientSignOff.InvalidSigner.selector); | ||
_ownableRecipientSignMock.transferOwnership(_newOwner.addr, deadline, signature); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added this only to fix the version to
1
, if that is not desirable I will remove the constructor.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @Amxx, just pinging to make sure you want this suggestion applied (considering the comment that I previously made).