NGOLis
The NGOLis contract manages staking and withdrawal functionalities for Non-Governmental Organizations (NGOs). It enables users to stake ETH, stETH, or wstETH while allocating a portion to NGO operations. The contract interacts with the Lido Finance protocol for staking and the withdrawal queue for managing withdrawals. It incorporates percentage-based sharing mechanisms, reward distributions, and comprehensive security controls. It includes various functions and modifiers to manage NGO operations, such as creating and updating NGO details, handling donations, and ensuring compliance with specific rules. The notBanned
modifier is used to restrict access to stake, ensuring that only non-banned entities can stake while everyone can withdraw their staked funds. Supports staking and withdrawal support for ETH, stETH, wstETH tokens.
Contract Architecture
Upgrade Mechanism
The contract implements the UUPS (Universal Upgradeable Proxy Standard) pattern:
Uses OpenZeppelin's
UUPSUpgradeable
frameworkUpgrades are controlled through the
_authorizeUpgrade
functionOnly the contract owner can initiate upgrades
State variables are preserved during upgrades through proxy delegation
Upgrade Control
The upgrade process is controlled through two main components:
Authorization Function
Authorizes contract upgrades in the UUPS pattern.
Access Control:
Restricted to contract owner
Parameters:
newImplementation: Address of new implementation contract
Security Considerations:
Critical function for contract upgradeability
Must be called through proxy
Requires careful implementation validation
Should maintain contract invariants
Usage:
Part of upgrade process
Called automatically by upgrade mechanism
Validates upgrade permissions
Proxy Pattern
Uses ERC1967 proxy standard
Maintains separation of concerns
Preserves contract state
Upgrade Process
Owner initiates upgrade with new implementation
_authorizeUpgrade
validates permissionProxy updates implementation pointer
State remains intact in proxy storage
Core Logic and Operations
Staking Mechanism
Percentage Calculation
NGO share percentage uses a base of 10000 for precise calculations
100% = 10000 and 1% = 100
Valid range: MIN_SHARE_PERCENT (100 = 1%) to MAX_SHARE_PERCENT (10000 = 100%)
Example: 5000 represents 50% share to NGO
Minimum Stake Requirements
Minimum stake amount: 1000 wei (defined by MIN_AMOUNT constant)
All stake operations require user not to be banned
Stakes cannot be made after NGO is marked as finished
Asset Distribution
User portion: (100% - NGO percentage) of staked amount
NGO portion: NGO percentage of staked amount
Rewards are calculated based on the User portion and donations on NGO portion
Token Support
Native ETH (
stake
)Liquid staked ETH (
stakeStEth
)Wrapped staked ETH (
stakeWStEth
)
Reward Management
Reward Calculation
Rewards tracked through lastNGOBalance
pendingRewardsCalculation() updates pending rewards by:
Converting current NGO assets to stETH value
Comparing with last recorded balance
Converting difference to wstETH
Adding to pendingNGORewards
Reward Distribution
Handled by oracle through handleNGOShareDistribution
Calculate pending rewards
Take platform fee (5% - LIS_FEE)
Transfer fee to platform address
Transfer remaining rewards to rewardsOwner
Update lastNGOBalance
Withdrawal System
Request Process
Users initiate withdrawals through
requestWithdrawals
Amount constraints:
Minimum: Set by Lido withdrawal queue
Maximum: Set by Lido withdrawal queue
Cannot exceed user's total balance (direct assets + NGO share)
Minimum withdrawal amount: 100 wei (MIN_WITHDRAWAL_AMOUNT)
Withdrawal Methods
Queue-based ETH withdrawal
Request withdrawal via requestWithdrawals
Claim via claimWithdrawal after finalization
Requires withdrawal request ID
Direct token withdrawal
claimWithdrawInStEth: Withdraw in stETH
claimWithdrawInWStEth: Withdraw in wstETH
No queue waiting period
Minimum withdrawal: 100 wei
Asset Recalculation
Withdrawals trigger proportional reduction of:
User's direct assets
NGO shares
Total NGO assets
Inheritance Structure
Sequential Diagram
Contract Storage Structure
State Variables
Key Storage Mappings
Important Constants
Data Structures
Enums
Type of eth in function for event
Structs
Struct representing initial stake information for a user.
Function Documentation
Initialization
constructor
constructor
Disables initializers at deployment.
initialize
initialize
Initializes the NGO contract.
Parameters
lidoSCAddress
: Lido staking contract_rewardOwnerAddress
: Rewards recipientwithdrawalSCAddress
: Withdrawal queue contractowner
: NGO owner addressoracle
: Oracle address_wstETHSC
: Wrapped stETH contract
Validation
No zero addresses allowed
Effects
Initializes base contracts
Sets up initial configuration
Staking Functions
stake
stake
Stakes native ETH in the NGO contract.
Parameters
_ngoPercent
: NGO share percentage (100 = 1%, 10000 = 100%)
Constraints
Must not be banned
NGO must not be finished
Minimal amount to stake: 1000 wei
Percentage must be between MIN_SHARE_PERCENT and MAX_SHARE_PERCENT
stakeStEth
stakeStEth
Stakes stETH tokens in the NGO contract.
Parameters
amount
: Amount of stETH to stake (in wei)_ngoPercent
: NGO share percentage (100 = 1%, 10000 = 100%)
Requirements
Caller must have approved contract for stETH transfer
Same constraints as
stake
stakeWStEth
stakeWStEth
Stakes wstETH tokens directly in the NGO contract.
Parameters
amount
: Amount of wstETH to stake (in wei)_ngoPercent
: NGO share percentage (100 = 1%, 10000 = 100%)
Requirements
User must approve contract for wstETH transfer before calling
Directly uses wstETH without wrapping
Same constraints as other stake functions
Withdrawal Functions
requestWithdrawals
requestWithdrawals
Initiates a withdrawal request.
Parameters
_amount
: Amount of ETH to withdraw (in wei)_id
: Stake identifier
Constraints
Amount must be within Lido's min/max limits
Range: min - 100 wei , max - 1000 ETH
Amount cannot exceed user's balance for given stake ID
claimWithdrawal
claimWithdrawal
Claims completed withdrawal request.
Parameters
_requestId
: Withdrawal request identifier
Requirements
Request must be finalized in Lido queue
Caller must be original requester
claimWithdrawInStEth
claimWithdrawInStEth
Claims stETH withdrawal. Note: expected amount to withdraw: min - 100 wei.
Parameters
_amount
: Amount to withdraw_id
: Stake identifier
Effects
Transfers stETH
Updates state
claimWithdrawInWStEth
claimWithdrawInWStEth
Claims wstETH withdrawal.
Note: expected amount to withdraw: min - 100 wei.
Parameters
_amount
: Amount to withdraw_id
: Stake identifier
Effects
Transfers wstETH
Updates state
withdrawCalculation
withdrawCalculation
Internal function for processing withdrawal mathematics.
Parameters
_amount
: Amount to withdraw in stETH_id
: Stake identifier
Returns
_amountWstETH
: Converted amount in wstETH
Calculation Steps With Explanation
Calculate User's NGO Share
Retrieves the user's share of NGO tokens for the specific stake ID
This represents the user's portion of the total NGO shares
Convert NGO Share to Assets
Converts user's NGO shares to actual wstETH tokens
Formula:
user_ngo_share * total_ngo_assets / total_ngo_shares
This determines how many wstETH tokens user's NGO shares represent
Calculate Total User Assets
Combines user's direct assets and NGO share assets
assets[msg.sender][_id]
: Direct wstETH holdings_ngoAssets
: Converted NGO share in wstETHRepresents total user's withdrawable wstETH
Convert Withdrawal Amount to wstETH
Converts requested stETH withdrawal amount to wstETH
Uses Lido's conversion rate
Necessary because internal accounting uses wstETH
Calculate Withdrawal Ratio
Determines what fraction of user's total assets is being withdrawn
DIVIDER
(10 * 10**17) used for precisionFormula:
(withdrawal_amount * DIVIDER) / total_user_assets
This ratio is used to proportionally reduce both direct assets and NGO shares
State Updates Based on Calculations
Update User's Direct Assets
Reduces user's direct assets proportionally based on withdrawal ratio
Maintains correct balance proportion
Update NGO Assets
Calculates and removes withdrawn portion from NGO assets
Maintains NGO asset accounting
Update NGO Shares
Reduces total NGO shares
Updates user's NGO share balance
Maintains share proportions
Update NGO Balance
Updates the tracked stETH balance
Used for reward calculations
Example Calculation Let's say:
User has 100 wstETH direct assets
User has NGO shares worth 50 wstETH
Total assets = 150 wstETH
User wants to withdraw 30 stETH
Convert 30 stETH to wstETH (say 25 wstETH)
Calculate ratio: 25 * DIVIDER / 150 = 0.1667 * DIVIDER
Reduce direct assets: 100 * 0.1667 = -16.67 wstETH
Reduce NGO assets: 50 * 0.1667 = -8.33 wstETH
Final balances:
Direct assets: 83.33 wstETH
NGO share: 41.67 wstETH
Total withdrawn: 25 wstETH
Important Considerations
All calculations maintain proportional relationships
Precision is maintained using DIVIDER
Both user assets and NGO shares are reduced proportionally
Conversion between stETH and wstETH is handled appropriately
This function is critical for maintaining correct balances and proportions during withdrawals while ensuring both user assets and NGO shares are properly accounted for.
Internal Calculation Functions
pendingRewardsCalculation
Calculates and updates pending rewards for the NGO based on asset value changes.
Process
Converts current NGO assets to stETH value using wstETHSC.getStETHByWstETH()
Compares current balance with lastNGOBalance
If current balance is higher:
Calculates the difference
Converts difference to wstETH
Adds to pendingNGORewards
Reduces totalNGOAssets by the reward amount
State Changes
Updates pendingNGORewards
Modifies totalNGOAssets
Usage
Called before stake asset calculations
Called during withdrawal processing
Called during reward distribution
Dependencies:
Requires wstETHSC interface for token conversions
Relies on lastNGOBalance for change detection
assetsCalculation
Calculates and distributes staked assets between user and NGO portions.
Parameters:
_amount: Amount being staked in wstETH
_percent: NGO share percentage (100-10000)
Process:
NGO Asset Calculation
Calculates NGO portion using percentage
Assigns remaining amount to user's direct assets
Share Distribution
Creates stake information record
Calculates NGO shares based on total assets
State Updates
Updates user's asset record
Increments total NGO assets
Updates share allocations
Updates lastNGOBalance
State Changes:
Updates assets[msg.sender][id]
Updates _userToStakeInfo mapping
Modifies totalNGOAssets
Updates totalNGOShares
Updates ngoShares mapping
Sets lastNGOBalance
Usage:
Called by stake()
Called by stakeStEth()
Called by stakeWStEth()
Administrative Functions
handleNGOShareDistribution
handleNGOShareDistribution
Manages the distribution of NGO rewards.
Access Control
Only callable by oracle
Calculations
Converts current NGO assets to stETH value
Calculates reward difference since last distribution
Takes LIS platform fee (5%)
Effects
Updates NGO asset totals
Transfers fees to platform
Transfers remaining rewards to NGO
Updates last balance tracking
setOracle
setOracle
Manages oracle permissions.
Parameters
_newOracle
: Address to modify oracle status_state
: True to grant oracle role, false to revoke
setRewardsOwner
setRewardsOwner
Updates rewards recipient.
Parameters
_newRewOwner
: New recipient address
endNGO
endNGO
Terminates the NGO contract.
Effects
Sets
isFinish
to truePrevents new stakes
Allows existing withdrawals
setUserBan
setUserBan
Controls user access to staking functionality.
Parameters
userAddress
: Address to ban/unbanisBan
: True to ban, false to unban
Access Control
Only callable by owner
Effects
Updates user's ban status
Banned users can withdraw but cannot stake
emitEvent
emitEvent
Emits NGO metadata for indexing.
Parameters
_name
: NGO name_imageLink
: NGO image URL_description
: NGO description_link
: NGO website/resource link_location
: NGO location
Access Control
Only callable by owner
Usage
Used for updating NGO information in external systems
Enables graph indexing of NGO details
System Functions
receive
receive
Fallback function to receive ETH.
Usage
Enables direct ETH transfers to contract
Required for stake operations with native ETH
No additional logic executed
View Functions
getUserBalance
getUserBalance
Returns user's total balance including NGO share.
Parameters
_user
: User address_id
: Stake identifier
Returns
Total balance in stETH
getUserStakeInfo
getUserStakeInfo
Returns stake details for given user and ID.
Parameters
_user
: User address_id
: Stake identifier
Returns
Struct containing:
percent
: NGO share percentageamount
: Original stake amountstartDate
: Stake timestamp
Modifiers
onlyOracle
Modifier to restrict access to only oracle.
notFinished
Modifier to restrict access to only oracle.
validStake
Modifier to check valid stake info.
notBanned
Modifier to check is user is banned.
validAmount
Modifier to check valid minimum amount for ETH and stETH.
validWithdrawalAmount
Modifier to check valid minimum amount for ETH and stETH.
Events
Staking Events
Staked
Emitted when a user stakes funds in the NGO.
Parameters
_id
: Unique identifier for the stake_staker
: Address of the staking user_amountStaked
: Amount of tokens staked_percentShare
: Percentage shared with NGO (100-10000)_ngo
: Address of the NGO contract_timestamp
: Block timestamp of staking_blockNumber
: Block number of staking_ethType
: Type of staked token (Native/StEth/WStEth)
Tracks: stake ID, staker address, amount, NGO share percentage, timestamp
Usage: Track new stakes and type of tokens used
Reward Events
RewardsUpdated
Emitted when NGO rewards are distributed.
Parameters
totalNGOAssets
: Amount of tokens in the NGO pool._timestamp
: Block timestamp of distribution_blockNumber
: Block number of distribution
Tracks: total tokens in NGO pool, share distribution timestamp and block number.
Usage: Monitor reward distributions and track NGO earnings
Withdrawal Events
WithdrawRequested
Emitted when a withdrawal request is initiated.
Parameters
_staker
: The address of the user requesting withdrawal_ngo
: The address of the NGO contract_requestId
: The ID of the withdrawal request_timestamp
: The block timestamp when withdraw was requested_blockNumber
: The block number when withdraw was requested_stakeId
: The id of the stake
Tracks: user address, request ID, stake ID
Usage: Track withdrawal requests in the queue
WithdrawClaimed
Emitted when a user claims a withdrawal.
Parameters
_claimer
: The address of the user claiming withdrawal_ngo
: The address of the NGO contract_amount
: The amount requested for withdrawal_requestId
: The ID of the withdrawal request_timestamp
: The block timestamp when withdraw was claimed_blockNumber
: The block number when withdraw was claimed
Tracks: claimer, amount, request ID
Usage: Track completed ETH withdrawals
WithdrawERC20Claimed
Emitted when stETH or wstETH withdrawal is claimed.
Parameters
_claimer
: The address of the user claiming withdrawal_ngo
: The address of the NGO contract_amount
: The amount of stEth or WStEth claimed_timestamp
: The block timestamp when withdraw was claimed_blockNumber
: The block number when withdraw was claimed_stakeId
: The id of the stake_ethType
: Type of Eth (stETH/wstETH)
Note: if _ethType = 1 then amount of stETH is passed. if _ethType = 2 then amount of wstETH is passed.
Tracks: claimer, amount, token type
Usage: Track completed stETH or wstETH withdrawals
Administrative Events
GraphEvent
Emitted when NGO metadata is updated.
Parameters
_name
: The name of the NGO_imageLink
: The link to the image associated with the NGO_description
: A description of the NGO_link
: A link associated with the NGO_location
: Physical location of NGO_ngo
: The address of the NGO contract_timestamp
: The block timestamp when withdraw was claimed
Tracks: NGO details and location
Usage: Track NGO metadata updates for indexing
NGOFinished
Emitted when NGO operations are terminated.
Parameters
_ngo
: The address of the NGO contract_timestamp
: The timestamp when the NGO was finished_blockNumber
: The block number when the NGO was finished
Tracks: termination timestamp and block number and address of NGO Contract.
Usage: Signal end of NGO contract.
OracleChanged
Emitted when the oracle was added or status was changed.
Parameters
_newOracle
: New oracle address_state
:Current oracle state
Note: If state = true - current address active. If state = false - current address inactive.
Tracks: New oracle address and current oracle state.
Usage: Signals change of an Oracle.
RewardsOwnerChanged
Emitted when the rewards owner was changed.
Parameters
_newRewOwner
: New reward owner address
Tracks: New Reward Owner address.
Usage: Signals change of Reward Owner.
BannedUser
Emitted when the state of user access is changed.
Parameters
_userAddress
: User address_isBan
: Current user state
Tracks: User address and access state of user.
Usage: Signals change of User access status.
Error Conditions
Validation Errors
InvalidPercent
Trigger: Share percentage outside valid range (100-10000)
Prevention: Validate percentage before staking
Impact: Prevents invalid share calculations
InvalidStakeAmount
Trigger: Invalid Stake amount maybe less than 1000 wei
Prevention: Make sure the stake amount is greater than 1000
Impact: Ensures economic viability of staking
InvalidWithdrawalAmount
Trigger: Withdrawal request below minimum limit 100 wei
Prevention: Make sure the withdrawal amount is greater than 100 wei
Impact: Ensures economic viability of withdrawals
InvalidWithdrawAmount
Trigger: Withdrawal request below minimum limit 1 percent of stake
Prevention: Make sure the withdrawal amount is greater than 1% of your stake
Impact: Ensures economic viability of withdrawals
RequestAmountTooSmall
Trigger: Withdrawal request below minimum limit
Parameter:
_amount
- Requested amountPrevention: Check Lido's minimum withdrawal amount
Impact: Ensures economic viability of withdrawals
RequestAmountTooLarge
Trigger: Withdrawal request above maximum limit or user balance
Parameter:
_amount
- Requested amountPrevention: Check balance and Lido's maximum withdrawal amount
Impact: Prevents overdraw attempts
MinimumWstEthStakeError
Trigger: Amount of stake lower than minimum for wstETH
Prevention: Check balance and Lido's minimum stake amount
Impact: Prevents failed stake attempts
MinimumWstEthWithdrawError
Trigger: Amount of withdrawal lower than minimum for wstETH
Prevention: Check balance and Lido's minimum withdrawal amount
Impact: Prevents failed withdraw attempts
Access Control Errors
OnlyOracle
Trigger: Non-oracle address calling oracle-restricted function
Parameter:
_sender
- Unauthorized caller addressPrevention: Use correct oracle address
Impact: Protects reward distribution mechanism
UserBanned
Trigger: Banned user attempting to stake
Prevention: Check ban status before staking
Impact: Enforces user restrictions
State Errors
NgoFinished
Trigger: Attempting operations after NGO termination
Prevention: Check NGO status before operations
Impact: Prevents post-termination activities
NotFinalizedStatus
Trigger: Claiming withdrawal before finalization
Prevention: Wait for withdrawal finalization
Impact: Ensures proper withdrawal sequence
RewardError
Trigger: Attempting reward distribution with no rewards
Prevention: Check reward balance before distribution
Impact: Prevents empty reward distributions
Technical Errors
NullAddress
Trigger: Providing zero address for critical parameters
Prevention: Validate addresses before use
Impact: Prevents contract initialization with invalid addresses
InvalidRequestIdForUser
Trigger: Claiming withdrawal for wrong request ID
Parameters:
_claimer
: Address attempting claim_requestId
: Invalid request ID
Prevention: Verify request ownership
Impact: Protects withdrawal ownership
ZeroAmount
Trigger: Attempting operation with zero amount
Prevention: Validate amount before operation
Impact: Prevents meaningless transactions
FeeError
Trigger: Attempting to transfer zero fee
Prevention: Ensure fee is not zero
Impact: Handles failed fee transfers
Security Features
Administrative Controls
Owner-only administrative functions
Contract upgrades (_authorizeUpgrade)
Oracle management (setOracle)
Rewards owner management (setRewardsOwner)
User banning (setUserBan)
NGO termination (endNGO)
Oracle-only functions
Reward distribution (handleNGOShareDistribution)
User Restrictions
Ban system for malicious users
Banned users can withdraw but cannot stake
Safety Checks
Reentrancy protection on withdrawals
Amount validation
Balance verification
Null address checks
Upgradability
UUPS proxy pattern
Owner-controlled upgrades
State preservation during upgrades
Minimum Withdrawal Limit Protection
When withdrawing assets from the smart contract, there is a built-in protection mechanism that enforces a minimum withdrawal amount based on the total staked amount. This prevents potential attacks through micro-withdrawals.
Example:
Total Stake: 1000 ETH
Attempted Withdrawal: 999 wei
Result: Transaction reverts
Due to Solidity's integer division rules, the ratio calculation for very small withdrawals relative to the stake amount will round to 0, making such withdrawals impossible.
Key Point: The minimum withdrawal amount automatically scales with the size of the stake. For a 1000 ETH stake, the minimum withdrawal is 1000 wei.
Last updated