Skip to content

Commit 55e7948

Browse files
authored
Merge pull request #112 from solidstate-network/transitive-owner
Add transitive owner utilities
2 parents c1b947d + b069391 commit 55e7948

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

contracts/access/ownable/OwnableInternal.sol

+24-2
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@
22

33
pragma solidity ^0.8.0;
44

5+
import { AddressUtils } from '../../utils/AddressUtils.sol';
6+
import { IERC173 } from '../IERC173.sol';
57
import { IOwnableInternal } from './IOwnableInternal.sol';
68
import { OwnableStorage } from './OwnableStorage.sol';
79

810
abstract contract OwnableInternal is IOwnableInternal {
11+
using AddressUtils for address;
912
using OwnableStorage for OwnableStorage.Layout;
1013

1114
modifier onlyOwner() {
15+
require(msg.sender == _owner(), 'Ownable: sender must be owner');
16+
_;
17+
}
18+
19+
modifier onlyTransitiveOwner() {
1220
require(
13-
msg.sender == OwnableStorage.layout().owner,
14-
'Ownable: sender must be owner'
21+
msg.sender == _transitiveOwner(),
22+
'Ownable: sender must be transitive owner'
1523
);
1624
_;
1725
}
@@ -20,6 +28,20 @@ abstract contract OwnableInternal is IOwnableInternal {
2028
return OwnableStorage.layout().owner;
2129
}
2230

31+
function _transitiveOwner() internal view virtual returns (address) {
32+
address owner = _owner();
33+
34+
while (owner.isContract()) {
35+
try IERC173(owner).owner() returns (address transitiveOwner) {
36+
owner = transitiveOwner;
37+
} catch {
38+
return owner;
39+
}
40+
}
41+
42+
return owner;
43+
}
44+
2345
function _transferOwnership(address account) internal virtual {
2446
OwnableStorage.layout().setOwner(account);
2547
emit OwnershipTransferred(msg.sender, account);

contracts/access/ownable/OwnableMock.sol

+8
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,12 @@ contract OwnableMock is Ownable {
1010
constructor(address owner) {
1111
OwnableStorage.layout().setOwner(owner);
1212
}
13+
14+
function setOwner(address owner) external {
15+
OwnableStorage.layout().setOwner(owner);
16+
}
17+
18+
function __transitiveOwner() external view returns (address) {
19+
return _transitiveOwner();
20+
}
1321
}

test/access/Ownable.ts

+38
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
22
import { describeBehaviorOfOwnable } from '@solidstate/spec';
33
import { OwnableMock, OwnableMock__factory } from '@solidstate/typechain-types';
4+
import { expect } from 'chai';
5+
import { deployMockContract } from 'ethereum-waffle';
46
import { ethers } from 'hardhat';
57

68
describe('Ownable', function () {
@@ -20,4 +22,40 @@ describe('Ownable', function () {
2022
getOwner: async () => owner,
2123
getNonOwner: async () => nonOwner,
2224
});
25+
26+
describe('__internal', () => {
27+
describe('#_transitiveOwner()', () => {
28+
it('returns owner if owner is EOA', async () => {
29+
expect(await instance.callStatic.__transitiveOwner()).to.equal(
30+
owner.address,
31+
);
32+
});
33+
34+
it('returns owner if owner is not Ownable', async () => {
35+
const ownerInstance = await deployMockContract(owner, []);
36+
37+
await instance.setOwner(ownerInstance.address);
38+
39+
expect(await instance.callStatic.__transitiveOwner()).to.equal(
40+
ownerInstance.address,
41+
);
42+
});
43+
44+
it('returns transitive owner', async () => {
45+
const secondOwnerInstance = await new OwnableMock__factory(
46+
owner,
47+
).deploy(owner.address);
48+
49+
const firstOwnerInstance = await new OwnableMock__factory(owner).deploy(
50+
secondOwnerInstance.address,
51+
);
52+
53+
await instance.setOwner(firstOwnerInstance.address);
54+
55+
expect(await instance.callStatic.__transitiveOwner()).to.equal(
56+
owner.address,
57+
);
58+
});
59+
});
60+
});
2361
});

0 commit comments

Comments
 (0)