diff --git a/packages/asset/contracts/Asset.sol b/packages/asset/contracts/Asset.sol index 9fb6b826ca..46fcbf54e7 100644 --- a/packages/asset/contracts/Asset.sol +++ b/packages/asset/contracts/Asset.sol @@ -384,5 +384,21 @@ contract Asset is return "ASSET"; } - uint256[49] private __gap; + address private _owner; + + /// @notice Returns the owner of the contract + /// @return address of the owner + function owner() external view returns (address) { + return _owner; + } + + /// @notice Sets the owner of the contract + /// @param newOwner address of the new owner + function transferOwnership(address newOwner) external { + require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Asset: Unauthorized"); + _owner = newOwner; + emit OwnershipTransferred(_owner, newOwner); + } + + uint256[48] private __gap; } diff --git a/packages/asset/contracts/Catalyst.sol b/packages/asset/contracts/Catalyst.sol index 861713fc2c..a16cb20dc5 100644 --- a/packages/asset/contracts/Catalyst.sol +++ b/packages/asset/contracts/Catalyst.sol @@ -324,5 +324,21 @@ contract Catalyst is return "CATALYST"; } - uint256[49] private __gap; + address private _owner; + + /// @notice Returns the owner of the contract + /// @return address of the owner + function owner() external view returns (address) { + return _owner; + } + + /// @notice Sets the owner of the contract + /// @param newOwner address of the new owner + function transferOwnership(address newOwner) external { + require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Asset: Unauthorized"); + _owner = newOwner; + emit OwnershipTransferred(_owner, newOwner); + } + + uint256[48] private __gap; } diff --git a/packages/asset/contracts/interfaces/IAsset.sol b/packages/asset/contracts/interfaces/IAsset.sol index bc0bed29d2..14fbd02068 100644 --- a/packages/asset/contracts/interfaces/IAsset.sol +++ b/packages/asset/contracts/interfaces/IAsset.sol @@ -18,6 +18,7 @@ interface IAsset { } event TrustedForwarderChanged(address indexed newTrustedForwarderAddress); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /// @notice Mint new tokens /// @dev Only callable by the minter role diff --git a/packages/asset/contracts/interfaces/ICatalyst.sol b/packages/asset/contracts/interfaces/ICatalyst.sol index eb1d3e564e..eaa03c7cda 100644 --- a/packages/asset/contracts/interfaces/ICatalyst.sol +++ b/packages/asset/contracts/interfaces/ICatalyst.sol @@ -9,6 +9,7 @@ interface ICatalyst { event DefaultRoyaltyChanged(address indexed newDefaultRoyaltyRecipient, uint256 newDefaultRoyaltyAmount); event BaseURISet(string baseURI); event OperatorRegistrySet(address indexed registry); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /// @notice Mints a new token, limited to MINTER_ROLE only /// @param to The address that will own the minted token diff --git a/packages/asset/test/Asset.test.ts b/packages/asset/test/Asset.test.ts index 6ede4a03bd..6bdea8f5e1 100644 --- a/packages/asset/test/Asset.test.ts +++ b/packages/asset/test/Asset.test.ts @@ -23,6 +23,34 @@ describe('Base Asset Contract (/packages/asset/contracts/Asset.sol)', function ( expect(await AssetContract.symbol()).to.be.equal('ASSET'); }); }); + describe('Owner', function () { + it('should not have the owner set when initially deployed', async function () { + const {AssetContract} = await runAssetSetup(); + expect(await AssetContract.owner()).to.be.equal( + ethers.constants.AddressZero + ); + }); + it('allows DEFAULT_ADMIN_ROLE to transfer the ownership', async function () { + const {AssetContract, assetAdmin} = await runAssetSetup(); + await AssetContract.connect(assetAdmin).transferOwnership( + assetAdmin.address + ); + expect(await AssetContract.owner()).to.be.equals(assetAdmin.address); + }); + it('does not allow non-DEFAULT_ADMIN_ROLE account to transfer the ownership', async function () { + const {AssetContract, deployer} = await runAssetSetup(); + await expect( + AssetContract.connect(deployer).transferOwnership(deployer.address) + ).to.be.revertedWith('Asset: Unauthorized'); + }); + it('emits OwnershipTransferred event when DEFAULT_ADMIN_ROLE transfers the ownership', async function () { + const {AssetContract, assetAdmin} = await runAssetSetup(); + const tx = await AssetContract.connect(assetAdmin).transferOwnership( + assetAdmin.address + ); + await expect(tx).to.emit(AssetContract, 'OwnershipTransferred'); + }); + }); describe('Access Control', function () { it('should have MINTER_ROLE defined', async function () { const {AssetContract} = await runAssetSetup(); diff --git a/packages/asset/test/Catalyst.test.ts b/packages/asset/test/Catalyst.test.ts index 01af6daccf..685d46239e 100644 --- a/packages/asset/test/Catalyst.test.ts +++ b/packages/asset/test/Catalyst.test.ts @@ -259,6 +259,32 @@ describe('Catalyst (/packages/asset/contracts/Catalyst.sol)', function () { ).to.revertedWith('Catalyst: CID cant be empty'); }); }); + describe('Owner', function () { + it('should not have the owner set when initially deployed', async function () { + const {catalyst} = await runCatalystSetup(); + expect(await catalyst.owner()).to.be.equal(ethers.constants.AddressZero); + }); + it('allows DEFAULT_ADMIN_ROLE to transfer the ownership', async function () { + const {catalyst, catalystAdmin} = await runCatalystSetup(); + await catalyst + .connect(catalystAdmin) + .transferOwnership(catalystAdmin.address); + expect(await catalyst.owner()).to.be.equals(catalystAdmin.address); + }); + it('does not allow non-DEFAULT_ADMIN_ROLE account to transfer the ownership', async function () { + const {catalyst, deployer} = await runCatalystSetup(); + await expect( + catalyst.connect(deployer).transferOwnership(deployer.address) + ).to.be.revertedWith('Asset: Unauthorized'); + }); + it('emits OwnershipTransferred event when DEFAULT_ADMIN_ROLE transfers the ownership', async function () { + const {catalyst, catalystAdmin} = await runCatalystSetup(); + const tx = await catalyst + .connect(catalystAdmin) + .transferOwnership(catalystAdmin.address); + await expect(tx).to.emit(catalyst, 'OwnershipTransferred'); + }); + }); describe('Admin Role', function () { it('Admin can set minter', async function () { const {catalystAsAdmin, user1, minterRole} = await runCatalystSetup(); diff --git a/packages/asset/test/fixtures/asset/assetFixture.ts b/packages/asset/test/fixtures/asset/assetFixture.ts index 7ecff10e3a..e4f486657b 100644 --- a/packages/asset/test/fixtures/asset/assetFixture.ts +++ b/packages/asset/test/fixtures/asset/assetFixture.ts @@ -74,6 +74,7 @@ export function generateAssetId( export async function runAssetSetup() { const [ + deployer, assetAdmin, owner, secondOwner, @@ -272,6 +273,7 @@ export async function runAssetSetup() { AssetContractAsMinter, AssetContractAsBurner, AssetContractAsAdmin, + deployer, owner, assetAdmin, minter,