-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
337 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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,88 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; | ||
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | ||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; | ||
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; | ||
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; | ||
|
||
import {IProvidersDelegator} from "../interfaces/delegate/IProvidersDelegator.sol"; | ||
import {IOwnable} from "../interfaces/utils/IOwnable.sol"; | ||
|
||
contract DelegatorFactory is OwnableUpgradeable, PausableUpgradeable, UUPSUpgradeable { | ||
address public lumerinDiamond; | ||
address public beacon; | ||
mapping(address => address[]) public proxies; | ||
|
||
event ProxyDeployed(address indexed proxyAddress); | ||
event ImplementationUpdated(address indexed newImplementation); | ||
|
||
constructor() { | ||
_disableInitializers(); | ||
} | ||
|
||
function DelegatorFactory_init(address _lumerinDiamond, address _implementation) external initializer { | ||
__Pausable_init(); | ||
__Ownable_init(); | ||
__UUPSUpgradeable_init(); | ||
|
||
lumerinDiamond = _lumerinDiamond; | ||
|
||
beacon = address(new UpgradeableBeacon(_implementation)); | ||
} | ||
|
||
function pause() external onlyOwner { | ||
_pause(); | ||
} | ||
|
||
function unpause() external onlyOwner { | ||
_unpause(); | ||
} | ||
|
||
function deployProxy( | ||
address feeTreasury_, | ||
uint256 fee_, | ||
string memory name_, | ||
string memory endpoint_ | ||
) external whenNotPaused returns (address) { | ||
bytes32 salt_ = _calculatePoolSalt(_msgSender()); | ||
address proxy_ = address(new BeaconProxy{salt: salt_}(beacon, bytes(""))); | ||
|
||
proxies[_msgSender()].push(address(proxy_)); | ||
|
||
IProvidersDelegator(proxy_).ProvidersDelegator_init(lumerinDiamond, feeTreasury_, fee_, name_, endpoint_); | ||
IOwnable(proxy_).transferOwnership(_msgSender()); | ||
|
||
emit ProxyDeployed(address(proxy_)); | ||
|
||
return address(proxy_); | ||
} | ||
|
||
function predictProxyAddress(address _deployer) external view returns (address) { | ||
bytes32 salt_ = _calculatePoolSalt(_deployer); | ||
|
||
bytes32 bytecodeHash_ = keccak256( | ||
abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(address(beacon), bytes(""))) | ||
); | ||
|
||
return Create2.computeAddress(salt_, bytecodeHash_); | ||
} | ||
|
||
function updateImplementation(address _newImplementation) external onlyOwner { | ||
UpgradeableBeacon(beacon).upgradeTo(_newImplementation); | ||
|
||
emit ImplementationUpdated(_newImplementation); | ||
} | ||
|
||
function version() external pure returns (uint256) { | ||
return 1; | ||
} | ||
|
||
function _calculatePoolSalt(address sender_) internal view returns (bytes32) { | ||
return keccak256(abi.encodePacked(sender_, proxies[sender_].length)); | ||
} | ||
|
||
function _authorizeUpgrade(address) internal view override onlyOwner {} | ||
} |
This file contains 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,15 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
interface IOwnable { | ||
/** | ||
* @dev Returns the address of the current owner. | ||
*/ | ||
function owner() external view returns (address); | ||
|
||
/** | ||
* @dev Transfers ownership of the contract to a new account (`newOwner`). | ||
* Can only be called by the current owner. | ||
*/ | ||
function transferOwnership(address newOwner_) external; | ||
} |
This file contains 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,12 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | ||
|
||
contract UUPSMock is UUPSUpgradeable { | ||
function version() external pure returns (uint256) { | ||
return 999; | ||
} | ||
|
||
function _authorizeUpgrade(address) internal view override {} | ||
} |
This file contains 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,200 @@ | ||
import { | ||
DelegatorFactory, | ||
LumerinDiamond, | ||
MorpheusToken, | ||
ProvidersDelegator, | ||
ProvidersDelegator__factory, | ||
UUPSMock, | ||
} from '@ethers-v6'; | ||
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; | ||
import { expect } from 'chai'; | ||
import { ethers } from 'hardhat'; | ||
|
||
import { wei } from '@/scripts/utils/utils'; | ||
import { | ||
deployDelegatorFactory, | ||
deployFacetMarketplace, | ||
deployFacetProviderRegistry, | ||
deployLumerinDiamond, | ||
deployMORToken, | ||
} from '@/test/helpers/deployers'; | ||
import { Reverter } from '@/test/helpers/reverter'; | ||
|
||
describe('DelegatorFactory', () => { | ||
const reverter = new Reverter(); | ||
|
||
let OWNER: SignerWithAddress; | ||
let KYLE: SignerWithAddress; | ||
let SHEV: SignerWithAddress; | ||
|
||
let diamond: LumerinDiamond; | ||
let delegatorFactory: DelegatorFactory; | ||
|
||
let token: MorpheusToken; | ||
|
||
before(async () => { | ||
[OWNER, KYLE, SHEV] = await ethers.getSigners(); | ||
|
||
[diamond, token] = await Promise.all([deployLumerinDiamond(), deployMORToken()]); | ||
await Promise.all([ | ||
deployFacetProviderRegistry(diamond), | ||
deployFacetMarketplace(diamond, token, wei(0.0001), wei(900)), | ||
]); | ||
|
||
delegatorFactory = await deployDelegatorFactory(diamond); | ||
|
||
await reverter.snapshot(); | ||
}); | ||
|
||
afterEach(reverter.revert); | ||
|
||
describe('UUPS', () => { | ||
describe('#DelegatorFactory_init', () => { | ||
it('should revert if try to call init function twice', async () => { | ||
await expect(delegatorFactory.DelegatorFactory_init(OWNER, OWNER)).to.be.rejectedWith( | ||
'Initializable: contract is already initialized', | ||
); | ||
}); | ||
}); | ||
describe('#version', () => { | ||
it('should return correct version', async () => { | ||
expect(await delegatorFactory.version()).to.eq(1); | ||
}); | ||
}); | ||
describe('#upgradeTo', () => { | ||
it('should upgrade to the new version', async () => { | ||
const factory = await ethers.getContractFactory('UUPSMock'); | ||
const newImpl = await factory.deploy(); | ||
|
||
await delegatorFactory.upgradeTo(newImpl); | ||
const newDelegatorFactory = newImpl.attach(delegatorFactory) as UUPSMock; | ||
|
||
expect(await newDelegatorFactory.version()).to.eq(999); | ||
}); | ||
it('should throw error when caller is not an owner', async () => { | ||
await expect(delegatorFactory.connect(KYLE).upgradeTo(KYLE)).to.be.revertedWith( | ||
'Ownable: caller is not the owner', | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('#deployProxy', () => { | ||
let providersDelegatorFactory: ProvidersDelegator__factory; | ||
|
||
before(async () => { | ||
providersDelegatorFactory = await ethers.getContractFactory('ProvidersDelegator'); | ||
}); | ||
|
||
it('should deploy a new proxy', async () => { | ||
await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); | ||
|
||
const proxy = providersDelegatorFactory.attach(await delegatorFactory.proxies(SHEV, 0)) as ProvidersDelegator; | ||
|
||
expect(await proxy.owner()).to.eq(SHEV); | ||
expect(await proxy.fee()).to.eq(wei(0.1, 25)); | ||
expect(await proxy.feeTreasury()).to.eq(KYLE); | ||
expect(await proxy.name()).to.eq('name'); | ||
expect(await proxy.endpoint()).to.eq('endpoint'); | ||
}); | ||
it('should deploy new proxies', async () => { | ||
await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name1', 'endpoint1'); | ||
await delegatorFactory.connect(SHEV).deployProxy(SHEV, wei(0.2, 25), 'name2', 'endpoint2'); | ||
await delegatorFactory.connect(KYLE).deployProxy(SHEV, wei(0.3, 25), 'name3', 'endpoint3'); | ||
|
||
let proxy = providersDelegatorFactory.attach(await delegatorFactory.proxies(SHEV, 1)) as ProvidersDelegator; | ||
expect(await proxy.owner()).to.eq(SHEV); | ||
expect(await proxy.fee()).to.eq(wei(0.2, 25)); | ||
expect(await proxy.feeTreasury()).to.eq(SHEV); | ||
expect(await proxy.name()).to.eq('name2'); | ||
expect(await proxy.endpoint()).to.eq('endpoint2'); | ||
|
||
proxy = providersDelegatorFactory.attach(await delegatorFactory.proxies(KYLE, 0)) as ProvidersDelegator; | ||
expect(await proxy.owner()).to.eq(KYLE); | ||
expect(await proxy.fee()).to.eq(wei(0.3, 25)); | ||
expect(await proxy.feeTreasury()).to.eq(SHEV); | ||
expect(await proxy.name()).to.eq('name3'); | ||
expect(await proxy.endpoint()).to.eq('endpoint3'); | ||
}); | ||
describe('#pause, #unpause', () => { | ||
it('should revert when paused and not after the unpause', async () => { | ||
await delegatorFactory.pause(); | ||
await expect( | ||
delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name1', 'endpoint1'), | ||
).to.be.rejectedWith('Pausable: paused'); | ||
|
||
await delegatorFactory.unpause(); | ||
await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name1', 'endpoint1'); | ||
}); | ||
it('should throw error when caller is not an owner', async () => { | ||
await expect(delegatorFactory.connect(KYLE).pause()).to.be.revertedWith('Ownable: caller is not the owner'); | ||
}); | ||
it('should throw error when caller is not an owner', async () => { | ||
await expect(delegatorFactory.connect(KYLE).unpause()).to.be.revertedWith('Ownable: caller is not the owner'); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('#predictProxyAddress', () => { | ||
it('should predict a proxy address', async () => { | ||
const predictedProxyAddress = await delegatorFactory.predictProxyAddress(SHEV); | ||
await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); | ||
|
||
const proxyAddress = await delegatorFactory.proxies(SHEV, 0); | ||
|
||
expect(proxyAddress).to.eq(predictedProxyAddress); | ||
}); | ||
it('should predict proxy addresses', async () => { | ||
let predictedProxyAddress = await delegatorFactory.predictProxyAddress(SHEV); | ||
await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); | ||
expect(await delegatorFactory.proxies(SHEV, 0)).to.eq(predictedProxyAddress); | ||
|
||
predictedProxyAddress = await delegatorFactory.predictProxyAddress(SHEV); | ||
await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); | ||
expect(await delegatorFactory.proxies(SHEV, 1)).to.eq(predictedProxyAddress); | ||
|
||
predictedProxyAddress = await delegatorFactory.predictProxyAddress(KYLE); | ||
await delegatorFactory.connect(KYLE).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); | ||
expect(await delegatorFactory.proxies(KYLE, 0)).to.eq(predictedProxyAddress); | ||
|
||
predictedProxyAddress = await delegatorFactory.predictProxyAddress(SHEV); | ||
await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); | ||
expect(await delegatorFactory.proxies(SHEV, 2)).to.eq(predictedProxyAddress); | ||
}); | ||
}); | ||
|
||
describe('#updateImplementation', () => { | ||
it('should update proxies implementation', async () => { | ||
await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); | ||
await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); | ||
await delegatorFactory.connect(KYLE).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); | ||
|
||
const factory = await ethers.getContractFactory('UUPSMock'); | ||
const newImpl = await factory.deploy(); | ||
|
||
let proxy = factory.attach(await delegatorFactory.proxies(SHEV, 0)) as ProvidersDelegator; | ||
expect(await proxy.version()).to.eq(1); | ||
proxy = factory.attach(await delegatorFactory.proxies(SHEV, 1)) as ProvidersDelegator; | ||
expect(await proxy.version()).to.eq(1); | ||
proxy = factory.attach(await delegatorFactory.proxies(KYLE, 0)) as ProvidersDelegator; | ||
expect(await proxy.version()).to.eq(1); | ||
|
||
await delegatorFactory.updateImplementation(newImpl); | ||
|
||
proxy = factory.attach(await delegatorFactory.proxies(SHEV, 0)) as ProvidersDelegator; | ||
expect(await proxy.version()).to.eq(999); | ||
proxy = factory.attach(await delegatorFactory.proxies(SHEV, 1)) as ProvidersDelegator; | ||
expect(await proxy.version()).to.eq(999); | ||
proxy = factory.attach(await delegatorFactory.proxies(KYLE, 0)) as ProvidersDelegator; | ||
expect(await proxy.version()).to.eq(999); | ||
}); | ||
it('should throw error when caller is not an owner', async () => { | ||
await expect(delegatorFactory.connect(KYLE).updateImplementation(KYLE)).to.be.revertedWith( | ||
'Ownable: caller is not the owner', | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
// npm run generate-types && npx hardhat test "test/delegate/DelegatorFactory.test" | ||
// npx hardhat coverage --solcoverjs ./.solcover.ts --testfiles "test/delegate/DelegatorFactory.test" |
This file contains 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
20 changes: 20 additions & 0 deletions
20
smart-contracts/test/helpers/deployers/delegate/delegator-factory.ts
This file contains 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,20 @@ | ||
import { ethers } from 'hardhat'; | ||
|
||
import { DelegatorFactory, LumerinDiamond } from '@/generated-types/ethers'; | ||
|
||
export const deployDelegatorFactory = async (diamond: LumerinDiamond): Promise<DelegatorFactory> => { | ||
const [providersDelegatorImplFactory, delegatorFactoryImplFactory, proxyFactory] = await Promise.all([ | ||
ethers.getContractFactory('ProvidersDelegator'), | ||
ethers.getContractFactory('DelegatorFactory'), | ||
ethers.getContractFactory('ERC1967Proxy'), | ||
]); | ||
|
||
const delegatorFactoryImpl = await delegatorFactoryImplFactory.deploy(); | ||
const proxy = await proxyFactory.deploy(delegatorFactoryImpl, '0x'); | ||
const delegatorFactory = delegatorFactoryImpl.attach(proxy) as DelegatorFactory; | ||
|
||
const providersDelegatorImpl = await providersDelegatorImplFactory.deploy(); | ||
await delegatorFactory.DelegatorFactory_init(diamond, providersDelegatorImpl); | ||
|
||
return delegatorFactory; | ||
}; |
This file contains 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 |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './delegator-factory'; | ||
export * from './providers-delegator'; |