Add Ownable2Step extension with 2-step transfer (#3620)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: Francisco <frangio.1@gmail.com>pull/3667/head^2
parent
160bf1a6eb
commit
1f0e7cdf04
@ -0,0 +1,57 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "./Ownable.sol"; |
||||
|
||||
/** |
||||
* @dev Contract module which provides access control mechanism, where |
||||
* there is an account (an owner) that can be granted exclusive access to |
||||
* specific functions. |
||||
* |
||||
* By default, the owner account will be the one that deploys the contract. This |
||||
* can later be changed with {transferOwnership} and {acceptOwnership}. |
||||
* |
||||
* This module is used through inheritance. It will make available all functions |
||||
* from parent (Ownable). |
||||
*/ |
||||
abstract contract Ownable2Step is Ownable { |
||||
address private _pendingOwner; |
||||
|
||||
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); |
||||
|
||||
/** |
||||
* @dev Returns the address of the pending owner. |
||||
*/ |
||||
function pendingOwner() public view virtual returns (address) { |
||||
return _pendingOwner; |
||||
} |
||||
|
||||
/** |
||||
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. |
||||
* Can only be called by the current owner. |
||||
*/ |
||||
function transferOwnership(address newOwner) public virtual override onlyOwner { |
||||
_pendingOwner = newOwner; |
||||
emit OwnershipTransferStarted(owner(), newOwner); |
||||
} |
||||
|
||||
/** |
||||
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. |
||||
* Internal function without access restriction. |
||||
*/ |
||||
function _transferOwnership(address newOwner) internal virtual override { |
||||
delete _pendingOwner; |
||||
super._transferOwnership(newOwner); |
||||
} |
||||
|
||||
/** |
||||
* @dev The new owner accepts the ownership transfer. |
||||
*/ |
||||
function acceptOwnership() external { |
||||
address sender = _msgSender(); |
||||
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner"); |
||||
_transferOwnership(sender); |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../access/Ownable2Step.sol"; |
||||
|
||||
contract Ownable2StepMock is Ownable2Step {} |
@ -0,0 +1,57 @@ |
||||
const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); |
||||
const { ZERO_ADDRESS } = constants; |
||||
const { expect } = require('chai'); |
||||
|
||||
const Ownable2Step = artifacts.require('Ownable2StepMock'); |
||||
|
||||
contract('Ownable2Step', function (accounts) { |
||||
const [owner, accountA, accountB] = accounts; |
||||
|
||||
beforeEach(async function () { |
||||
this.ownable2Step = await Ownable2Step.new({ from: owner }); |
||||
}); |
||||
|
||||
describe('transfer ownership', function () { |
||||
it('starting a transfer does not change owner', async function () { |
||||
const receipt = await this.ownable2Step.transferOwnership(accountA, { from: owner }); |
||||
expectEvent(receipt, 'OwnershipTransferStarted', { previousOwner: owner, newOwner: accountA }); |
||||
expect(await this.ownable2Step.owner()).to.equal(owner); |
||||
expect(await this.ownable2Step.pendingOwner()).to.equal(accountA); |
||||
}); |
||||
|
||||
it('changes owner after transfer', async function () { |
||||
await this.ownable2Step.transferOwnership(accountA, { from: owner }); |
||||
const receipt = await this.ownable2Step.acceptOwnership({ from: accountA }); |
||||
expectEvent(receipt, 'OwnershipTransferred', { previousOwner: owner, newOwner: accountA }); |
||||
expect(await this.ownable2Step.owner()).to.equal(accountA); |
||||
expect(await this.ownable2Step.pendingOwner()).to.not.equal(accountA); |
||||
}); |
||||
|
||||
it('changes owner after renouncing ownership', async function () { |
||||
await this.ownable2Step.renounceOwnership({ from: owner }); |
||||
// If renounceOwnership is removed from parent an alternative is needed ...
|
||||
// without it is difficult to cleanly renounce with the two step process
|
||||
// see: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3620#discussion_r957930388
|
||||
expect(await this.ownable2Step.owner()).to.equal(ZERO_ADDRESS); |
||||
}); |
||||
|
||||
it('pending owner resets after renouncing ownership', async function () { |
||||
await this.ownable2Step.transferOwnership(accountA, { from: owner }); |
||||
expect(await this.ownable2Step.pendingOwner()).to.equal(accountA); |
||||
await this.ownable2Step.renounceOwnership({ from: owner }); |
||||
expect(await this.ownable2Step.pendingOwner()).to.equal(ZERO_ADDRESS); |
||||
await expectRevert( |
||||
this.ownable2Step.acceptOwnership({ from: accountA }), |
||||
'Ownable2Step: caller is not the new owner', |
||||
); |
||||
}); |
||||
|
||||
it('guards transfer against invalid user', async function () { |
||||
await this.ownable2Step.transferOwnership(accountA, { from: owner }); |
||||
await expectRevert( |
||||
this.ownable2Step.acceptOwnership({ from: accountB }), |
||||
'Ownable2Step: caller is not the new owner', |
||||
); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue