A Cairo 1.0 smart contract for Starknet that enables validator revenue sharing with proportional distribution of ETH and ERC20 tokens.
- Validator management with proportional share distribution
- Support for both ETH and ERC20 tokens
- Pull-based claim model for secure token distribution
- Rate-limited distributions to prevent abuse
- Token whitelist for security
- Admin controls for validator and token management
- Total shares must always equal 10000 (100%)
- Shares are specified in basis points (1/100th of a percent)
- Automatic share redistribution on validator removal
- Share updates with validation
- Native ETH support
- ERC20 token support with whitelist
- Token management (add/remove supported tokens)
- Separate balances and distributions for each token
- Admin-only sensitive operations
- Rate limiting for distributions
- Token whitelist
- Pull-based claiming
- Non-zero address validation
- Share total validation
fn add_validator(ref self: ContractState, validator: Validator)
fn remove_validator(ref self: ContractState, addr: ContractAddress)
fn update_share(ref self: ContractState, addr: ContractAddress, new_share: u16)fn deposit_eth(ref self: ContractState)
fn deposit_erc20(ref self: ContractState, token: ContractAddress, amount: u256)
fn distribute_eth(ref self: ContractState)
fn distribute_erc20(ref self: ContractState, token: ContractAddress)
fn claim(ref self: ContractState, token: ContractAddress)fn add_supported_token(ref self: ContractState, token: ContractAddress)
fn remove_supported_token(ref self: ContractState, token: ContractAddress)fn get_validator_count(self: @ContractState) -> u32
fn get_validator(self: @ContractState, index: u32) -> Validator
fn get_all_validators(self: @ContractState) -> Array<Validator>
fn get_total_shares(self: @ContractState) -> u16
fn get_claimable(self: @ContractState, token: ContractAddress, addr: ContractAddress) -> u256
fn get_balance_token(self: @ContractState, token: ContractAddress) -> u256
fn get_balance_eth(self: @ContractState) -> u256
fn get_supported_tokens(self: @ContractState) -> Array<ContractAddress>
fn get_last_distribution_time(self: @ContractState, token: ContractAddress) -> u64#[event]
struct ValidatorAdded {
addr: ContractAddress,
share: u16,
}
#[event]
struct ValidatorRemoved {
addr: ContractAddress,
redistributed_share: u16,
}
#[event]
struct ValidatorShareUpdated {
addr: ContractAddress,
old_share: u16,
new_share: u16,
}
#[event]
struct TokenDeposited {
token: ContractAddress,
amount: u256,
depositor: ContractAddress,
}
#[event]
struct TokenDistributed {
token: ContractAddress,
total_amount: u256,
}
#[event]
struct Claimed {
token: ContractAddress,
claimer: ContractAddress,
amount: u256,
}
#[event]
struct TokenAdded {
token: ContractAddress,
}
#[event]
struct TokenRemoved {
token: ContractAddress,
}
#[event]
struct EthDeposited {
amount: u256,
depositor: ContractAddress,
}
#[event]
struct EthDistributed {
total_amount: u256,
}-
Constructor Tests
- ✓ Deploy with valid validators (total shares = 10000)
- ✗ Deploy with invalid total shares (≠ 10000)
- ✗ Deploy with zero address validator
- ✓ Deploy with single validator (100% share)
- ✓ Deploy with multiple validators (shares sum to 100%)
-
Add Validator Tests
- ✓ Add validator with valid share
- ✗ Add validator when not admin
- ✗ Add validator with zero address
- ✗ Add validator causing total shares > 10000
- ✓ Add validator emits correct event
- ✗ Add duplicate validator address
-
Remove Validator Tests
- ✓ Remove validator redistributes shares proportionally
- ✗ Remove validator when not admin
- ✗ Remove non-existent validator
- ✓ Remove validator updates total shares correctly
- ✓ Remove validator emits correct event
- ✓ Remove last validator fails (must maintain at least one)
-
Update Share Tests
- ✓ Update share maintains 10000 total
- ✗ Update share when not admin
- ✗ Update share for non-existent validator
- ✓ Update share emits correct event
- ✗ Update share causing total ≠ 10000
-
Token Support Tests
- ✓ Add valid ERC20 token to whitelist
- ✗ Add non-ERC20 contract to whitelist
- ✗ Add token when not admin
- ✓ Remove token from whitelist
- ✗ Remove token when not admin
- ✗ Remove non-existent token
-
ETH Handling Tests
- ✓ Deposit ETH updates balance
- ✓ Distribute ETH to validators
- ✓ Claim ETH updates balances
- ✗ Distribute ETH with zero balance
- ✗ Distribute ETH before minimum interval
- ✓ Get ETH balance returns correct amount
-
ERC20 Handling Tests
- ✓ Deposit ERC20 transfers tokens correctly
- ✓ Distribute ERC20 calculates shares correctly
- ✓ Claim ERC20 transfers tokens correctly
- ✗ Deposit non-whitelisted token fails
- ✗ Distribute ERC20 with zero balance
- ✗ Distribute ERC20 before minimum interval
- ✓ Get ERC20 balance returns correct amount
-
Access Control Tests
- ✗ Non-admin adds validator
- ✗ Non-admin removes validator
- ✗ Non-admin updates share
- ✗ Non-admin adds token
- ✗ Non-admin removes token
-
Rate Limiting Tests
- ✗ Distribute before minimum interval (ETH)
- ✗ Distribute before minimum interval (ERC20)
- ✓ Distribute after minimum interval (ETH)
- ✓ Distribute after minimum interval (ERC20)
-
Share Validation Tests
- ✗ Total shares exceed 10000
- ✗ Total shares below 10000
- ✓ Share updates maintain 10000 total
- ✓ Share redistribution maintains 10000 total
-
Multi-Token Scenario
- ✓ Multiple token deposits
- ✓ Multiple token distributions
- ✓ Multiple token claims
- ✓ Mixed ETH and ERC20 operations
-
Validator Lifecycle
- ✓ Add validator → Update share → Remove validator
- ✓ Multiple validator operations with distributions
- ✓ Share redistribution with pending claims
- ETH amount in
deposit_ethis currently hardcoded to 1 as we need to find a way to get the actual ETH amount from the transaction. - The test framework (snforge) compatibility issues prevent automated test execution.
[dependencies]
starknet = "2.11.4"
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v2.0.0" }- Install Scarb
- Clone the repository
- Build the contract:
scarb buildMIT