diff --git a/contracts/interfaces/ITTUDeployer.sol b/contracts/interfaces/ITTUDeployer.sol index 3b40375..bc18cd3 100644 --- a/contracts/interfaces/ITTUDeployer.sol +++ b/contracts/interfaces/ITTUDeployer.sol @@ -41,35 +41,13 @@ interface ITTUDeployer { */ function feeCollector() external returns (ITTUFeeCollector); - /** - * @notice Deploys and configures a new set of TokenTable products. - * @dev Emits `TokenTableSuiteDeployed`. Throws: `AlreadyDeployed`. - * @param projectToken The project token address. - * @param projectId A unique projectId, otherwise it will revert. - * @param isUpgradeable When set to false, a `Clone` instead of a `BeaconProxy` is created to prevent future upgradeability. - * @param isTransferable Allow FutureToken to be transferable. - * @param isCancelable Allow unlocking schedules to be cancelled in the Unlocker. - * @param isHookable Allow Unlocker to call an external hook. - * @param isWithdrawable Allow the founder to withdraw deposited funds. - */ - function deployTTSuite( - address projectToken, - string calldata projectId, - bool isUpgradeable, - bool isTransferable, - bool isCancelable, - bool isHookable, - bool isWithdrawable - ) - external - returns (ITokenTableUnlockerV2, ITTFutureTokenV2, ITTTrackerTokenV2); - /** * @notice Deploys and configures a new set of TokenTable products that uses an existing NFT as FutureToken. * @dev Emits `TokenTableSuiteDeployed`. Throws: `AlreadyDeployed`. * @param projectToken The project token address. * @param projectId A unique projectId, otherwise it will revert. * @param isUpgradeable When set to false, a `Clone` instead of a `BeaconProxy` is created to prevent future upgradeability. + * @param isTransferable Allow FutureToken to be transferable. Ignored if an existing FutureToken is supplied. * @param isCancelable Allow unlocking schedules to be cancelled in the Unlocker. * @param isHookable Allow Unlocker to call an external hook. * @param isWithdrawable Allow the founder to withdraw deposited funds. @@ -77,10 +55,11 @@ interface ITTUDeployer { function deployTTSuite( address projectToken, address existingFutureToken, - string calldata projectId, + string memory projectId, bool isUpgradeable, + bool isTransferable, bool isCancelable, bool isHookable, bool isWithdrawable - ) external returns (ITokenTableUnlockerV2, ITTTrackerTokenV2); + ) external returns (ITokenTableUnlockerV2, ITTFutureTokenV2); } diff --git a/contracts/proxy/TTUDeployerLite.sol b/contracts/proxy/TTUDeployerLite.sol index ba4a6c6..a892c66 100644 --- a/contracts/proxy/TTUDeployerLite.sol +++ b/contracts/proxy/TTUDeployerLite.sol @@ -35,16 +35,14 @@ contract TTUDeployerLite is ITTUDeployer, Ownable, IVersionable { function deployTTSuite( address projectToken, - string calldata projectId, + address existingFutureToken, + string memory projectId, bool isUpgradeable, bool isTransferable, bool isCancelable, bool isHookable, bool isWithdrawable - ) - external - returns (ITokenTableUnlockerV2, ITTFutureTokenV2, ITTTrackerTokenV2) - { + ) external returns (ITokenTableUnlockerV2, ITTFutureTokenV2) { if (registry[projectId]) revert AlreadyDeployed(); registry[projectId] = true; @@ -54,132 +52,70 @@ contract TTUDeployerLite is ITTUDeployer, Ownable, IVersionable { if (!isUpgradeable) { (unlocker, futureToken, trackerToken) = _deployClonesAndInitialize( projectToken, - address(0), - isTransferable, + existingFutureToken, + false, isCancelable, isHookable, isWithdrawable ); } else { - futureToken = ITTFutureTokenV2( - address( - new BeaconProxy( - address( - beaconManager.beacons( - beaconManager.getId("TTFutureTokenV2") + if (existingFutureToken == address(0)) { + futureToken = ITTFutureTokenV2( + address( + new BeaconProxy( + address(beaconManager.beacons("TTFutureTokenV2")), + abi.encodeWithSelector( + ITTFutureTokenV2.initialize.selector, + projectToken, + isTransferable ) - ), - abi.encodeWithSelector( - ITTFutureTokenV2.initialize.selector, - projectToken, - isTransferable - ) - ) - ) - ); - unlocker = ITokenTableUnlockerV2( - address( - new BeaconProxy( - address( - beaconManager.beacons( - beaconManager.getId("TokenTableUnlockerV2") - ) - ), - abi.encodeWithSelector( - ITokenTableUnlockerV2.initialize.selector, - projectToken, - futureToken, - this, - isCancelable, - isHookable, - isWithdrawable, - false ) ) - ) - ); - trackerToken = ITTTrackerTokenV2( - address( - new BeaconProxy( - address( - beaconManager.beacons( - beaconManager.getId("TTTrackerTokenV2") + ); + unlocker = ITokenTableUnlockerV2( + address( + new BeaconProxy( + address( + beaconManager.beacons("TokenTableUnlockerV2") + ), + abi.encodeWithSelector( + ITokenTableUnlockerV2.initialize.selector, + projectToken, + address(futureToken), + this, + isCancelable, + isHookable, + isWithdrawable, + true ) - ), - abi.encodeWithSelector( - ITTTrackerTokenV2.initialize.selector, - address(unlocker) ) ) - ) - ); - } - unlocker.transferOwnership(msg.sender); - futureToken.setAuthorizedMinterSingleUse(address(unlocker)); - emit TokenTableSuiteDeployed( - msg.sender, - projectId, - address(unlocker), - address(futureToken), - address(trackerToken) - ); - return (unlocker, futureToken, trackerToken); - } - - function deployTTSuite( - address projectToken, - address existingFutureToken, - string calldata projectId, - bool isUpgradeable, - bool isCancelable, - bool isHookable, - bool isWithdrawable - ) external returns (ITokenTableUnlockerV2, ITTTrackerTokenV2) { - if (registry[projectId]) revert AlreadyDeployed(); - registry[projectId] = true; - - ITTFutureTokenV2 placeholder; - ITokenTableUnlockerV2 unlocker; - ITTTrackerTokenV2 trackerToken; - if (!isUpgradeable) { - (unlocker, placeholder, trackerToken) = _deployClonesAndInitialize( - projectToken, - existingFutureToken, - false, - isCancelable, - isHookable, - isWithdrawable - ); - } else { - unlocker = ITokenTableUnlockerV2( - address( - new BeaconProxy( - address( - beaconManager.beacons( - beaconManager.getId("TTUV2ExternalFT") + ); + futureToken.setAuthorizedMinterSingleUse(address(unlocker)); + } else { + unlocker = ITokenTableUnlockerV2( + address( + new BeaconProxy( + address(beaconManager.beacons("TTUV2ExternalFT")), + abi.encodeWithSelector( + ITokenTableUnlockerV2.initialize.selector, + projectToken, + existingFutureToken, + this, + isCancelable, + isHookable, + isWithdrawable, + true ) - ), - abi.encodeWithSelector( - ITokenTableUnlockerV2.initialize.selector, - projectToken, - existingFutureToken, - this, - isCancelable, - isHookable, - isWithdrawable, - true ) ) - ) - ); + ); + futureToken = ITTFutureTokenV2(existingFutureToken); + } trackerToken = ITTTrackerTokenV2( address( new BeaconProxy( - address( - beaconManager.beacons( - beaconManager.getId("TTTrackerTokenV2") - ) - ), + address(beaconManager.beacons("TTTrackerTokenV2")), abi.encodeWithSelector( ITTTrackerTokenV2.initialize.selector, address(unlocker) @@ -193,10 +129,10 @@ contract TTUDeployerLite is ITTUDeployer, Ownable, IVersionable { msg.sender, projectId, address(unlocker), - address(existingFutureToken), + address(futureToken), address(trackerToken) ); - return (unlocker, trackerToken); + return (unlocker, futureToken); } function version() public pure virtual returns (string memory) { @@ -222,9 +158,7 @@ contract TTUDeployerLite is ITTUDeployer, Ownable, IVersionable { if (existingFutureToken == address(0)) { futureToken = ITTFutureTokenV2( Clones.clone( - beaconManager - .beacons(beaconManager.getId("TTFutureTokenV2")) - .implementation() + beaconManager.beacons("TTFutureTokenV2").implementation() ) ); futureToken.initialize(projectToken, isTransferable); @@ -233,9 +167,7 @@ contract TTUDeployerLite is ITTUDeployer, Ownable, IVersionable { } unlocker = ITokenTableUnlockerV2( Clones.clone( - beaconManager - .beacons(beaconManager.getId("TokenTableUnlockerV2")) - .implementation() + beaconManager.beacons("TokenTableUnlockerV2").implementation() ) ); unlocker.initialize( @@ -248,11 +180,12 @@ contract TTUDeployerLite is ITTUDeployer, Ownable, IVersionable { ); trackerToken = ITTTrackerTokenV2( Clones.clone( - beaconManager - .beacons(beaconManager.getId("TTTrackerTokenV2")) - .implementation() + beaconManager.beacons("TTTrackerTokenV2").implementation() ) ); trackerToken.initialize(address(unlocker)); + if (existingFutureToken == address(0)) { + futureToken.setAuthorizedMinterSingleUse(address(unlocker)); + } } } diff --git a/contracts/proxy/TTUV2BeaconManager.sol b/contracts/proxy/TTUV2BeaconManager.sol index 2500257..736a39e 100644 --- a/contracts/proxy/TTUV2BeaconManager.sol +++ b/contracts/proxy/TTUV2BeaconManager.sol @@ -16,17 +16,18 @@ import {IVersionable} from "../interfaces/IVersionable.sol"; * This contract should be deployed using TTUDeployer. */ contract TTUV2BeaconManager is Ownable, IVersionable { - mapping(bytes32 => UpgradeableBeacon) public beacons; + // Reserved names: TokenTableUnlockerV2, TTFutureTokenV2, TTTrackerTokenV2 + mapping(string => UpgradeableBeacon) public beacons; constructor() Ownable(_msgSender()) {} function upgradeCustomBeacon( - bytes32 id, + string calldata id, address newImpl ) external onlyOwner { UpgradeableBeacon beacon = beacons[id]; if (address(beacon) == address(0)) { - beacon = new UpgradeableBeacon(newImpl, address(this)); + beacons[id] = new UpgradeableBeacon(newImpl, address(this)); } else { beacon.upgradeTo(newImpl); } @@ -35,9 +36,4 @@ contract TTUV2BeaconManager is Ownable, IVersionable { function version() external pure returns (string memory) { return "2.6.0"; } - - // Reserved names: TokenTableUnlockerV2, TTFutureTokenV2, TTTrackerTokenV2 - function getId(string memory name) external pure returns (bytes32) { - return keccak256(abi.encode(name)); - } } diff --git a/hardhat.config.ts b/hardhat.config.ts index 260cb4a..f914929 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -286,6 +286,6 @@ export default { exclude: ['libraries', 'mock'] }, gasReporter: { - enabled: true + enabled: false } } diff --git a/test/TokenTableUnlockerV2.test.ts b/test/TokenTableUnlockerV2.test.ts index b8de09d..a3c7389 100644 --- a/test/TokenTableUnlockerV2.test.ts +++ b/test/TokenTableUnlockerV2.test.ts @@ -925,26 +925,30 @@ describe('V2', () => { const BeaconManagerFactory = await ethers.getContractFactory('TTUV2BeaconManager') - beaconManager = await BeaconManagerFactory.deploy( - await unlocker.getAddress(), - await futureToken.getAddress(), + beaconManager = await BeaconManagerFactory.deploy() + await beaconManager.upgradeCustomBeacon( + 'TokenTableUnlockerV2', + await unlocker.getAddress() + ) + await beaconManager.upgradeCustomBeacon( + 'TTFutureTokenV2', + await futureToken.getAddress() + ) + await beaconManager.upgradeCustomBeacon( + 'TTTrackerTokenV2', await trackerToken.getAddress() ) - await deployer.setBeaconManager( await beaconManager.getAddress() ) }) it('should deploy suite and complete beacon upgrade', async () => { - const [ - unlockerAddress, - futureTokenAddress, - trackerTokenAddress - ] = await deployer + const [unlockerAddress, futureTokenAddress] = await deployer .connect(founder) .deployTTSuite.staticCall( await projectToken.getAddress(), + ethers.ZeroAddress, projectId, true, true, @@ -952,17 +956,29 @@ describe('V2', () => { true, true ) - await deployer - .connect(founder) - .deployTTSuite( - await projectToken.getAddress(), - projectId, - true, - true, - true, - true, - true + + const deploymentReceipt = await ( + await deployer + .connect(founder) + .deployTTSuite( + await projectToken.getAddress(), + ethers.ZeroAddress, + projectId, + true, + true, + true, + true, + true + ) + ).wait() + + const trackerTokenAddress = ( + await deployer.queryFilter( + deployer.getEvent('TokenTableSuiteDeployed'), + deploymentReceipt?.blockNumber, + deploymentReceipt?.blockNumber ) + )[0].args.trackerToken const unlocker_ = await hre.ethers.getContractAt( 'TokenTableUnlockerV2', unlockerAddress @@ -1034,10 +1050,12 @@ describe('V2', () => { 'TTUV2BeaconManager', await deployer.beaconManager() ) - await beaconManager_.upgradeUnlocker( + await beaconManager_.upgradeCustomBeacon( + 'TokenTableUnlockerV2', await projectToken.getAddress() ) - await beaconManager_.upgradeFutureToken( + await beaconManager_.upgradeCustomBeacon( + 'TTFutureTokenV2', await projectToken.getAddress() ) await expect( @@ -1064,14 +1082,11 @@ describe('V2', () => { it('should deploy as clone and not upgrade', async () => { // Testing clone instead of beacon proxy - const [ - unlockerAddress2, - futureTokenAddress2, - trackerTokenAddress2 - ] = await deployer + const [unlockerAddress2, futureTokenAddress2] = await deployer .connect(founder) .deployTTSuite.staticCall( await projectToken.getAddress(), + ethers.ZeroAddress, projectId + projectId, false, true, @@ -1079,17 +1094,27 @@ describe('V2', () => { true, true ) - await deployer - .connect(founder) - .deployTTSuite( - await projectToken.getAddress(), - projectId + projectId, - false, - true, - true, - true, - true + const deploymentReceipt = await ( + await deployer + .connect(founder) + .deployTTSuite( + await projectToken.getAddress(), + ethers.ZeroAddress, + projectId + projectId, + false, + true, + true, + true, + true + ) + ).wait() + const trackerTokenAddress2 = ( + await deployer.queryFilter( + deployer.getEvent('TokenTableSuiteDeployed'), + deploymentReceipt?.blockNumber, + deploymentReceipt?.blockNumber ) + )[0].args.trackerToken const unlocker2 = await hre.ethers.getContractAt( 'TokenTableUnlockerV2', unlockerAddress2 @@ -1161,10 +1186,12 @@ describe('V2', () => { 'TTUV2BeaconManager', await deployer.beaconManager() ) - await beaconManager_.upgradeUnlocker( + await beaconManager_.upgradeCustomBeacon( + 'TokenTableUnlockerV2', await projectToken.getAddress() ) - await beaconManager_.upgradeFutureToken( + await beaconManager_.upgradeCustomBeacon( + 'TTFutureTokenV2', await projectToken.getAddress() ) // Clone should not revert