-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HUBS-224 crowdsale with individual locking staking periods #170
base: main
Are you sure you want to change the base?
Changes from 8 commits
4780b1a
e080796
8d83cfc
f4dd2ad
d9a4102
a806488
0a8cb52
a6b7a19
ded1b66
33cab65
c816611
48afc39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.18; | ||
|
||
import "forge-std/Script.sol"; | ||
import "forge-std/console.sol"; | ||
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | ||
import { IPNFT } from "../../src/IPNFT.sol"; | ||
import { IPermissioner, TermsAcceptedPermissioner } from "../../src/Permissioner.sol"; | ||
import { StakedLockingCrowdSale } from "../../src/crowdsale/StakedLockingCrowdSale.sol"; | ||
import { LockingCrowdSale, ITrustedLockingContracts } from "../../src/crowdsale/LockingCrowdSale.sol"; | ||
import { TimelockedToken } from "../../src/TimelockedToken.sol"; | ||
|
||
contract RolloutV25Sale is Script { | ||
function run() public { | ||
address moleculeDevMultisig = 0xCfA0F84660fB33bFd07C369E5491Ab02C449f71B; | ||
vm.startBroadcast(); | ||
|
||
TimelockedToken timelockedTokenImplementation = new TimelockedToken(); | ||
StakedLockingCrowdSale stakedLockingCrowdSale = new StakedLockingCrowdSale(timelockedTokenImplementation); | ||
stakedLockingCrowdSale.transferOwnership(moleculeDevMultisig); | ||
vm.stopBroadcast(); | ||
|
||
console.log("STAKED_LOCKING_CROWDSALE_ADDRESS=%s", address(stakedLockingCrowdSale)); | ||
console.log("timelocked token implementation=%s", address(timelockedTokenImplementation)); | ||
// 0x7c36c64DA1c3a2065074caa9C48e7648FB733aAB | ||
// vestedDaoToken.grantRole(vestedDaoToken.ROLE_CREATE_SCHEDULE(), address(stakedLockingCrowdSale)); | ||
// stakedLockingCrowdSale.trustVestingContract(vestedDaoToken); | ||
} | ||
} | ||
|
||
contract RolloutV25LockingSale is Script { | ||
function run() public { | ||
//mainnet 0xCfA0F84660fB33bFd07C369E5491Ab02C449f71B; | ||
address moleculeDevMultisig = 0x9d5a6ae551f1117946FF6e0e86ef9A1B20C90Cb0; | ||
|
||
ITrustedLockingContracts stakedLockingCrowdsale = ITrustedLockingContracts(0xd1cE2EA7d3b0C9cAB025A4aD762FC00315141ad7); | ||
TimelockedToken timelockedTokenImplementation = TimelockedToken(0xF8F79c1E02387b0Fc9DE0945cD9A2c06F127D851); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Validate contract addresses Add validation for the hardcoded contract addresses:
Example implementation: + require(address(stakedLockingCrowdsale) != address(0), "Invalid staked locking crowdsale");
+ require(address(timelockedTokenImplementation) != address(0), "Invalid timelocked token");
+ // Verify interface implementation
+ try stakedLockingCrowdsale.supportsInterface(type(ITrustedLockingContracts).interfaceId) returns (bool supported) {
+ require(supported, "Contract does not implement ITrustedLockingContracts");
+ } catch {
+ revert("Failed to verify interface support");
+ }
|
||
|
||
vm.startBroadcast(); | ||
LockingCrowdSale lockingCrowdsale = new LockingCrowdSale(timelockedTokenImplementation); | ||
lockingCrowdsale.trustLockingContractSource(stakedLockingCrowdsale); | ||
lockingCrowdsale.transferOwnership(moleculeDevMultisig); | ||
vm.stopBroadcast(); | ||
|
||
console.log("LOCKING_CROWDSALE_ADDRESS=%s", address(lockingCrowdsale)); | ||
//console.log("timelocked token implementation=%s", address(timelockedTokenImplementation)); | ||
// 0x7c36c64DA1c3a2065074caa9C48e7648FB733aAB | ||
// vestedDaoToken.grantRole(vestedDaoToken.ROLE_CREATE_SCHEDULE(), address(stakedLockingCrowdSale)); | ||
// stakedLockingCrowdSale.trustVestingContract(vestedDaoToken); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -12,29 +12,45 @@ import { CrowdSale, Sale } from "./CrowdSale.sol"; | |||||||||||||||||||||||||||
error UnsupportedInitializer(); | ||||||||||||||||||||||||||||
error InvalidDuration(); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
interface ITrustedLockingContracts { | ||||||||||||||||||||||||||||
function lockingContracts(address) external view returns (TimelockedToken); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||
* @title LockingCrowdSale | ||||||||||||||||||||||||||||
* @author molecule.to | ||||||||||||||||||||||||||||
* @notice a fixed price sales base contract that locks the sold tokens for a configurable duration | ||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||
contract LockingCrowdSale is CrowdSale { | ||||||||||||||||||||||||||||
contract LockingCrowdSale is CrowdSale, ITrustedLockingContracts { | ||||||||||||||||||||||||||||
using SafeERC20 for IERC20Metadata; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
mapping(uint256 => uint256) public salesLockingDuration; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/// @notice map from token address to reusable TimelockedToken contracts | ||||||||||||||||||||||||||||
mapping(address => TimelockedToken) public lockingContracts; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
address immutable lockingTokenImplementation = address(new TimelockedToken()); | ||||||||||||||||||||||||||||
///@notice this can be another contract registry that takes care of locking contracts | ||||||||||||||||||||||||||||
/// to reuse implementations | ||||||||||||||||||||||||||||
ITrustedLockingContracts public lockingContractTrustee; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
address immutable public TIMELOCKED_TOKEN_IMPLEMENTATION; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
event Started(uint256 indexed saleId, address indexed issuer, Sale sale, TimelockedToken lockingToken, uint256 lockingDuration, uint16 feeBp); | ||||||||||||||||||||||||||||
event LockingContractCreated(TimelockedToken indexed lockingContract, IERC20Metadata indexed underlyingToken); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
constructor(TimelockedToken _timelockedTokenImplementation) { | ||||||||||||||||||||||||||||
TIMELOCKED_TOKEN_IMPLEMENTATION = address(_timelockedTokenImplementation); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Comment on lines
+35
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add address validation in constructor. The constructor should validate that the provided TimelockedToken implementation address is not zero to prevent potential deployment issues. constructor(TimelockedToken _timelockedTokenImplementation) {
+ require(address(_timelockedTokenImplementation) != address(0), "Invalid implementation address");
TIMELOCKED_TOKEN_IMPLEMENTATION = address(_timelockedTokenImplementation);
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/// @dev disable parent sale starting functions | ||||||||||||||||||||||||||||
function startSale(Sale calldata) public pure override returns (uint256) { | ||||||||||||||||||||||||||||
revert UnsupportedInitializer(); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
function trustLockingContractSource(ITrustedLockingContracts _lockingContractTrustee) public onlyOwner { | ||||||||||||||||||||||||||||
lockingContractTrustee = _lockingContractTrustee; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||
* @notice allows anyone to create a timelocked token that's controlled by this sale contract | ||||||||||||||||||||||||||||
* helpful if you want to reuse the timelocked token for your own custom schedules | ||||||||||||||||||||||||||||
|
@@ -46,7 +62,12 @@ contract LockingCrowdSale is CrowdSale { | |||||||||||||||||||||||||||
lockedTokenContract = lockingContracts[address(underlyingToken)]; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
if (address(lockedTokenContract) == address(0)) { | ||||||||||||||||||||||||||||
lockedTokenContract = _makeNewLockedTokenContract(underlyingToken); | ||||||||||||||||||||||||||||
if (address(lockingContractTrustee) != address(0)) { | ||||||||||||||||||||||||||||
lockedTokenContract = lockingContractTrustee.lockingContracts(address(underlyingToken)); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
if (address(lockedTokenContract) == address(0)) { | ||||||||||||||||||||||||||||
lockedTokenContract = _makeNewLockedTokenContract(underlyingToken); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
lockingContracts[address(underlyingToken)] = lockedTokenContract; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
@@ -114,7 +135,7 @@ contract LockingCrowdSale is CrowdSale { | |||||||||||||||||||||||||||
* @return lockedTokenContract address of the new timelocked token contract | ||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||
function _makeNewLockedTokenContract(IERC20Metadata auctionToken) private returns (TimelockedToken lockedTokenContract) { | ||||||||||||||||||||||||||||
lockedTokenContract = TimelockedToken(Clones.clone(lockingTokenImplementation)); | ||||||||||||||||||||||||||||
lockedTokenContract = TimelockedToken(Clones.clone(TIMELOCKED_TOKEN_IMPLEMENTATION)); | ||||||||||||||||||||||||||||
lockedTokenContract.initialize(auctionToken); | ||||||||||||||||||||||||||||
emit LockingContractCreated(lockedTokenContract, auctionToken); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Comment on lines
+133
to
136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for external calls in _makeNewLockedTokenContract. The initialization call could fail but there's no error handling. Consider adding try-catch to handle potential initialization failures gracefully. function _makeNewLockedTokenContract(IERC20Metadata auctionToken) private returns (TimelockedToken lockedTokenContract) {
lockedTokenContract = TimelockedToken(Clones.clone(TIMELOCKED_TOKEN_IMPLEMENTATION));
- lockedTokenContract.initialize(auctionToken);
+ try lockedTokenContract.initialize(auctionToken) {
+ emit LockingContractCreated(lockedTokenContract, auctionToken);
+ } catch Error(string memory reason) {
+ revert(string.concat("Initialization failed: ", reason));
+ } catch {
+ revert("Initialization failed");
+ }
- emit LockingContractCreated(lockedTokenContract, auctionToken);
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Uncomment and implement role management
The commented code appears to handle critical role assignments:
This should either be:
Also applies to: 47-49