Skip to content

Commit

Permalink
Implemented PermanentOwnable contract with the test coverage (#82)
Browse files Browse the repository at this point in the history
* Implemented PermanentOwnable contract with the test coverage

* Refactored the code

* Integrated PermanentOwnable into ProxyBeacon and TransparentProxyUpgrader contracts

* Updated the patch version and refactored imports
  • Loading branch information
mllwchrry authored Jan 4, 2024
1 parent 4eea4dc commit 5f7b2b6
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 28 deletions.
47 changes: 47 additions & 0 deletions contracts/access-control/PermanentOwnable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
* @notice The PermanentOwnable module
*
* 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.
*
* The owner is set to the address provided by the deployer. The ownership cannot be further changed.
*
* This module will make available the modifier `onlyOwner`, which can be applied
* to your functions to restrict their use to the owners.
*/
abstract contract PermanentOwnable {
address private immutable _OWNER;

/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_onlyOwner();
_;
}

/**
* @notice Initializes the contract setting the address provided by the deployer as the owner.
* @param owner_ the address of the permanent owner.
*/
constructor(address owner_) {
require(owner_ != address(0), "PermanentOwnable: zero address can not be the owner");

_OWNER = owner_;
}

/**
* @notice Returns the address of the owner.
* @return the permanent owner.
*/
function owner() public view virtual returns (address) {
return _OWNER;
}

function _onlyOwner() internal view virtual {
require(_OWNER == msg.sender, "PermanentOwnable: caller is not the owner");
}
}
14 changes: 14 additions & 0 deletions contracts/mock/access-control/PermanentOwnableMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {PermanentOwnable} from "../../access-control/PermanentOwnable.sol";

contract PermanentOwnableMock is PermanentOwnable {
event ValidOwner();

constructor(address _owner) PermanentOwnable(_owner) {}

function onlyOwnerMethod() external onlyOwner {
emit ValidOwner();
}
}
2 changes: 1 addition & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solarity/solidity-lib",
"version": "2.6.12",
"version": "2.6.13",
"license": "MIT",
"author": "Distributed Lab",
"readme": "README.md",
Expand Down
15 changes: 4 additions & 11 deletions contracts/proxy/beacon/ProxyBeacon.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,22 @@ pragma solidity ^0.8.4;
import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

import {PermanentOwnable} from "../../access-control/PermanentOwnable.sol";

/**
* @notice The proxies module
*
* This is a lightweight utility ProxyBeacon contract that may be used as a beacon that BeaconProxies point to.
*/
contract ProxyBeacon is IBeacon {
contract ProxyBeacon is IBeacon, PermanentOwnable {
using Address for address;

address private immutable _OWNER;
constructor() PermanentOwnable(msg.sender) {}

address private _implementation;

event Upgraded(address implementation);

modifier onlyOwner() {
require(_OWNER == msg.sender, "ProxyBeacon: not an owner");
_;
}

constructor() {
_OWNER = msg.sender;
}

/**
* @notice The function to upgrade to implementation contract
* @param newImplementation_ the new implementation
Expand Down
15 changes: 4 additions & 11 deletions contracts/proxy/transparent/TransparentProxyUpgrader.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,17 @@ pragma solidity ^0.8.4;
import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

import {PermanentOwnable} from "../../access-control/PermanentOwnable.sol";

/**
* @notice The proxies module
*
* This is the lightweight helper contract that may be used as a TransparentProxy admin.
*/
contract TransparentProxyUpgrader {
contract TransparentProxyUpgrader is PermanentOwnable {
using Address for address;

address private immutable _OWNER;

modifier onlyOwner() {
require(_OWNER == msg.sender, "TransparentProxyUpgrader: not an owner");
_;
}

constructor() {
_OWNER = msg.sender;
}
constructor() PermanentOwnable(msg.sender) {}

/**
* @notice The function to upgrade the implementation contract
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solarity/solidity-lib",
"version": "2.6.12",
"version": "2.6.13",
"license": "MIT",
"author": "Distributed Lab",
"description": "Solidity Library by Distributed Lab",
Expand Down
48 changes: 48 additions & 0 deletions test/access-control/PermanentOwnable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ethers } from "hardhat";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
import { Reverter } from "@/test/helpers/reverter";
import { ZERO_ADDR } from "@/scripts/utils/constants";

import { PermanentOwnableMock } from "@ethers-v6";

describe("PermanentOwnable", () => {
const reverter = new Reverter();

let OWNER: SignerWithAddress;
let OTHER: SignerWithAddress;

let permanentOwnable: PermanentOwnableMock;

before("setup", async () => {
[OWNER, OTHER] = await ethers.getSigners();

const permanentOwnableMock = await ethers.getContractFactory("PermanentOwnableMock");
permanentOwnable = await permanentOwnableMock.deploy(OWNER);

await reverter.snapshot();
});

afterEach(reverter.revert);

describe("PermanentOwnable", () => {
it("should set the correct owner", async () => {
expect(await permanentOwnable.owner()).to.equal(OWNER.address);
});

it("should reject zero address during the owner initialization", async () => {
const permanentOwnableMock = await ethers.getContractFactory("PermanentOwnableMock");

await expect(permanentOwnableMock.deploy(ZERO_ADDR)).to.be.revertedWith(
"PermanentOwnable: zero address can not be the owner",
);
});

it("only owner should call this function", async () => {
expect(await permanentOwnable.connect(OWNER).onlyOwnerMethod()).to.emit(permanentOwnable, "ValidOwner");
await expect(permanentOwnable.connect(OTHER).onlyOwnerMethod()).to.be.revertedWith(
"PermanentOwnable: caller is not the owner",
);
});
});
});
2 changes: 1 addition & 1 deletion test/proxy/beacon/ProxyBeacon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("ProxyBeacon", () => {

it("only owner should upgrade", async () => {
await expect(proxyBeacon.connect(SECOND).upgradeTo(await token.getAddress())).to.be.revertedWith(
"ProxyBeacon: not an owner",
"PermanentOwnable: caller is not the owner",
);
});
});
Expand Down
2 changes: 1 addition & 1 deletion test/proxy/transparent/TransparentProxyUpgrader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe("TransparentProxyUpgrader", () => {
it("only owner should upgrade", async () => {
await expect(
transparentProxyUpgrader.connect(SECOND).upgrade(await proxy.getAddress(), await proxy.getAddress(), "0x"),
).to.be.revertedWith("TransparentProxyUpgrader: not an owner");
).to.be.revertedWith("PermanentOwnable: caller is not the owner");
});
});

Expand Down

0 comments on commit 5f7b2b6

Please sign in to comment.