InvestmentManager
Last updated
Last updated
Inherits:, Initializable, PausableUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, UUPSUpgradeable
Manages investment rounds and token vesting for the ecosystem
Implements a secure and upgradeable investment management system
Notes:
security-contact: security@nebula-labs.xyz
copyright: Copyright (c) 2025 Nebula Holding Inc. All rights reserved.
oz-upgrades:
Protects against gas limits when processing all investors during finalization
Maximum number of investors allowed per investment round
Ensures rounds are long enough for investor participation (5 days)
Minimum duration for an investment round in seconds
Prevents overly long rounds that might block capital (90 days)
Maximum duration for an investment round in seconds
Provides a security delay before implementation changes can be executed (3 days)
Duration of the timelock period for contract upgrades in seconds
Typically granted to guardian addresses for emergency security actions
Role identifier for addresses authorized to pause contract functions
Can perform administrative functions like emergency withdrawals
Role identifier for addresses authorized to manage investment operations
Controls the ability to schedule and execute contract upgrades
Role identifier for addresses authorized to upgrade the contract
Has control over creating investment rounds and major decisions
Role identifier for addresses representing the DAO's governance
Used for token transfers and vesting distributions
Reference to the ecosystem's ERC20 token
Destination for emergency withdrawals and holds elevated permissions
Address of the timelock controller
Receives ETH from finalized investment rounds
Address of the treasury contract
Tracks the supply commitment for all created rounds
Total amount of tokens allocated for investment rounds
Incremented on each upgrade to track contract versions
Implementation version of this contract
Core data structure that stores round configurations and status
Array of all investment rounds
Used to track all participants in each round
Maps round IDs to arrays of investor addresses
Tracks how much each investor has contributed to a round
Maps round IDs and investors to their invested ETH amount
Used to track deployed vesting contracts for each investor
Maps round IDs and investors to their vesting contract addresses
Stores maximum ETH and token amounts for each investor in a round
Maps round IDs and investors to their token allocations
Used to ensure round allocations don't exceed limits
Maps round IDs to the total token allocation for that round
Implements the timelock upgrade security pattern
Holds information about a pending contract upgrade
Prevents storage collision when adding new variables in upgrades
Reserved storage slots for future upgrades
Ensures the provided round ID exists
Note: throws: InvalidRound if roundId is out of bounds
Parameters
roundId
uint32
The ID of the investment round to check
Ensures the round is in active status
Note: throws: RoundNotActive if the round is not active
Parameters
roundId
uint32
The ID of the investment round to check
Ensures the round has a specific status
Note: throws: InvalidRoundStatus if the round doesn't have the required status
Parameters
roundId
uint32
The ID of the investment round to check
requiredStatus
RoundStatus
The status that the round should have
Ensures the provided address is not zero
Note: throws: ZeroAddressDetected if the address is zero
Parameters
addr
address
The address to check
Ensures the provided amount is not zero
Notes:
throws: InvalidAmount if the amount is zero
oz-upgrades-unsafe-allow: constructor
Automatically invests in the current active round
Fallback function that handles direct ETH transfers
Note: throws: NoActiveRound if no rounds are currently active
Sets up roles, connects to ecosystem contracts, and initializes version
Initializes the contract with essential parameters
Note: throws: ZeroAddressDetected if any parameter is the zero address
Parameters
token
address
Address of the ecosystem token
timelock_
address
Address of the timelock controller
treasury_
address
Address of the treasury contract
Pauses the contract's core functionality
When paused, most state-changing functions will revert
Emergency functions like withdrawals remain active while paused
Only callable by addresses with PAUSER_ROLE
Notes:
requires-role: PAUSER_ROLE
events-emits: {Paused} event from PausableUpgradeable
Unpauses the contract, resuming normal operations
Re-enables all functionality that was blocked during pause
Only callable by addresses with PAUSER_ROLE
Notes:
requires-role: PAUSER_ROLE
events-emits: {Unpaused} event from PausableUpgradeable
Schedules an upgrade to a new implementation contract
The upgrade cannot be executed until the timelock period has passed
Initiates the timelock period before an upgrade can be executed
Notes:
requires-role: UPGRADER_ROLE
security: Implements a timelock delay for added security
events-emits: {UpgradeScheduled} when upgrade is scheduled
throws: ZeroAddressDetected if newImplementation is zero address
throws: If called by address without UPGRADER_ROLE
Parameters
newImplementation
address
Address of the new implementation contract
Cancels a previously scheduled upgrade
Only callable by addresses with UPGRADER_ROLE
Only callable by addresses with MANAGER_ROLE
Emergency function to withdraw all tokens to the timelock
Notes:
throws: ZeroAddressDetected if token address is zero
throws: ZeroBalance if contract has no token balance
Parameters
token
address
The ERC20 token to withdraw
Only callable by addresses with MANAGER_ROLE
Emergency function to withdraw all ETH to the timelock
Note: throws: ZeroBalance if contract has no ETH balance
Creates a new investment round with specified parameters
Only callable by DAO_ROLE when contract is not paused
Notes:
requires-role: DAO_ROLE
security: Validates all parameters and checks token supply
events-emits: {CreateRound} when round is created
events-emits: {RoundStatusUpdated} when round status is set to PENDING
throws: InvalidDuration if duration is outside allowed range
throws: InvalidEthTarget if ethTarget is zero
throws: InvalidTokenAllocation if tokenAlloc is zero
throws: InvalidStartTime if start is in the past
throws: InsufficientSupply if contract lacks required tokens
Parameters
start
uint64
Timestamp when the round should start
duration
uint64
Length of the round in seconds
ethTarget
uint256
Target amount of ETH to raise in the round
tokenAlloc
uint256
Total number of tokens allocated for this round
vestingCliff
uint64
Duration in seconds before vesting begins
vestingDuration
uint64
Total duration of the vesting period in seconds
Returns
<none>
uint32
roundId The ID of the newly created round
Activates a pending investment round
Only callable by MANAGER_ROLE when contract is not paused
Notes:
requires-role: MANAGER_ROLE
security: Ensures round exists and is in PENDING status
security: Validates round timing constraints
events-emits: {RoundStatusUpdated} when round status is set to ACTIVE
modifiers: validRound, correctStatus(PENDING), whenNotPaused
throws: RoundStartTimeNotReached if current time is before start time
throws: RoundEndTimeReached if current time is after end time
throws: InvalidRound if roundId doesn't exist
throws: InvalidRoundStatus if round is not in PENDING status
Parameters
roundId
uint32
The ID of the round to activate
Adds a new investor allocation to a specific investment round
Only callable by MANAGER_ROLE when contract is not paused
Notes:
requires-role: MANAGER_ROLE
security: Validates round status and allocation limits
events-emits: {InvestorAllocated} when allocation is added
modifiers: validRound, whenNotPaused
throws: InvalidInvestor if investor address is zero
throws: InvalidEthAmount if ethAmount is zero
throws: InvalidTokenAmount if tokenAmount is zero
throws: InvalidRoundStatus if round is completed or finalized
throws: AllocationExists if investor already has an allocation
throws: ExceedsRoundAllocation if allocation exceeds round limit
Parameters
roundId
uint32
The ID of the investment round
investor
address
The address of the investor to allocate
ethAmount
uint256
Amount of ETH the investor is allowed to invest
tokenAmount
uint256
Amount of tokens the investor will receive
Removes an investor's allocation from a specific investment round
Only callable by MANAGER_ROLE when contract is not paused
Notes:
requires-role: MANAGER_ROLE
security: Ensures investor has no active position before removal
events-emits: {InvestorAllocationRemoved} when allocation is removed
modifiers: validRound, whenNotPaused
throws: InvalidInvestor if investor address is zero
throws: InvalidRoundStatus if round is not in PENDING or ACTIVE status
throws: NoAllocationExists if investor has no allocation
throws: InvestorHasActivePosition if investor has already invested
Parameters
roundId
uint32
The ID of the investment round
investor
address
The address of the investor whose allocation to remove
Allows an investor to cancel their investment in an active round
Returns the invested ETH to the investor and updates round state
Notes:
security: Uses nonReentrant guard for ETH transfers
security: Only allows cancellation during active round status
events-emits: {CancelInvestment} when investment is cancelled
modifiers: validRound, activeRound, nonReentrant
throws: NoInvestment if caller has no active investment
throws: InvalidRound if roundId doesn't exist
throws: RoundNotActive if round is not in active status
Parameters
roundId
uint32
The ID of the investment round
Finalizes a completed investment round
Deploys vesting contracts and distributes tokens to investors
Notes:
security: Uses nonReentrant guard for token transfers
security: Only callable when contract is not paused
security: Processes all investors in the round
events-emits: {RoundFinalized} when round is finalized
events-emits: {DeployVesting} for each vesting contract created
events-emits: {RoundStatusUpdated} when status changes to FINALIZED
modifiers: validRound, correctStatus(COMPLETED), nonReentrant, whenNotPaused
throws: InvalidRound if roundId doesn't exist
throws: InvalidRoundStatus if round is not in COMPLETED status
Parameters
roundId
uint32
The ID of the investment round to finalize
Cancels an investment round and returns tokens to treasury
Only callable by MANAGER_ROLE when contract is not paused
Notes:
requires-role: MANAGER_ROLE
security: Ensures round can only be cancelled in PENDING or ACTIVE status
security: Automatically returns allocated tokens to treasury
events-emits: {RoundStatusUpdated} when status changes to CANCELLED
events-emits: {RoundCancelled} when round is cancelled
modifiers: validRound, whenNotPaused
throws: InvalidRound if roundId doesn't exist
throws: InvalidRoundStatus if round is not in PENDING or ACTIVE status
Parameters
roundId
uint32
The ID of the investment round to cancel
Allows investors to claim ETH refunds from cancelled rounds
Refunds the full invested amount and updates round state
Notes:
security: Uses nonReentrant guard for ETH transfers
security: Ensures round is in CANCELLED status
security: Updates investor position and round state atomically
events-emits: {RefundClaimed} when refund is processed
modifiers: validRound, nonReentrant
throws: InvalidRound if roundId doesn't exist
throws: RoundNotCancelled if round is not in CANCELLED status
throws: NoRefundAvailable if caller has no refund to claim
Parameters
roundId
uint32
The ID of the investment round
Calculates remaining time in the upgrade timelock period
Returns 0 if no upgrade is pending or timelock has expired
Note: security: Helps track upgrade timelock status
Returns
<none>
uint256
uint256 Remaining time in seconds before upgrade can be executed
Gets the refundable amount for an investor in a cancelled round
Returns 0 if round is not cancelled or investor has no position
Note: security: Only returns values for cancelled rounds
Parameters
roundId
uint32
The ID of the investment round
investor
address
The address of the investor to check
Returns
<none>
uint256
uint256 Amount of ETH available for refund
Retrieves investment details for a specific investor in a round
Returns allocation amounts, invested amount, and vesting contract address
Parameters
roundId
uint32
The ID of the investment round to query
investor
address
The address of the investor to query
Returns
etherAmount
uint256
The maximum amount of ETH the investor can invest
tokenAmount
uint256
The amount of tokens allocated to the investor
invested
uint256
The amount of ETH already invested by the investor
vestingContract
address
The address of the investor's vesting contract
Gets the address of the ecosystem token
Provides access to the token contract address
Returns
<none>
address
address The address of the ecosystem token contract
Retrieves all information about a specific investment round
Returns the full Round struct with all round parameters
Parameters
roundId
uint32
The ID of the investment round to query
Returns
<none>
Round
Round Complete round information including status and allocations
Gets the list of investors in a specific round
Returns array of all investor addresses that participated
Parameters
roundId
uint32
The ID of the investment round to query
Returns
<none>
address[]
address[] Array of investor addresses in the round
Processes ETH investment in a specific investment round
Handles direct investment and fallback function investments
Notes:
security: Uses nonReentrant guard for ETH transfers
security: Validates round status and investor allocation
security: Enforces investment limits and round constraints
events-emits: {Invest} when investment is processed
events-emits: {RoundComplete} if round target is reached
modifiers: validRound, activeRound, whenNotPaused, nonReentrant
throws: RoundEnded if round end time has passed
throws: RoundOversubscribed if maximum investor count reached
throws: NoAllocation if sender has no allocation
throws: AmountAllocationMismatch if sent ETH doesn't match remaining allocation
Parameters
roundId
uint32
The ID of the investment round
Gets the ID of the currently active investment round
Iterates through all rounds to find one with ACTIVE status
Notes:
security: Returns max uint32 value as sentinel when no active round is found
view-stability: Does not modify state
Returns
<none>
uint32
uint32 The ID of the active round, or type(uint32).max if no active round exists
Authorizes and executes a contract upgrade after timelock period
Internal function called by the UUPS upgrade mechanism
Notes:
requires-role: UPGRADER_ROLE
security: Enforces timelock period before upgrade execution
security: Verifies upgrade was properly scheduled
security: Checks implementation address matches scheduled upgrade
events-emits: {Upgraded} when upgrade is executed
throws: UpgradeNotScheduled if no upgrade was scheduled
throws: ImplementationMismatch if implementation doesn't match scheduled
throws: UpgradeTimelockActive if timelock period hasn't elapsed
Parameters
newImplementation
address
Address of the new implementation contract
Updates the status of an investment round
Internal function to manage round state transitions
Notes:
security: Ensures status transitions are only forward-moving
events-emits: {RoundStatusUpdated} when status is changed
throws: InvalidStatusTransition if attempting to move to a previous status
Parameters
roundId
uint32
The ID of the investment round to update
newStatus
RoundStatus
The new status to set for the round
Processes an investment into a round
Handles investment accounting and status updates
Notes:
security: Updates investor tracking and round state atomically
events-emits: {Invest} when investment is processed
events-emits: {RoundComplete} if round target is reached
Parameters
roundId
uint32
The ID of the investment round
investor
address
The address of the investor
amount
uint256
The amount of ETH being invested
Removes an investor from a round's tracking array
Uses optimized removal pattern to maintain array integrity
Note: security: Maintains array consistency with gas-efficient removal
Parameters
roundId
uint32
The ID of the investment round
investor
address
The address of the investor to remove
Deploys a new vesting contract for an investor
Creates and configures a vesting schedule for allocated tokens
Notes:
security: Sets up vesting parameters based on round configuration
events-emits: {DeployVesting} when vesting contract is created
Parameters
investor
address
The address of the investor who will receive the tokens
allocation
uint256
The amount of tokens to be vested
roundId
uint32
The ID of the investment round
Returns
<none>
address
address The address of the newly deployed vesting contract