Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions cairo_deep_dive/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ edition = "2024_07"
# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html

[dependencies]
cairo_execute = "2.12.2"
cairo_execute = "2.11.4"

[cairo]
enable-gas = false

[dev-dependencies]
cairo_test = "2.12.2"

[[target.executable]]
name = "main"
Expand Down
10 changes: 0 additions & 10 deletions cairo_deep_dive/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ struct Wallet<T, U> {
}


#[executable]
fn main() -> u32 {
//Loop Demo
// let mut i: usize = 0;
Expand Down Expand Up @@ -133,12 +132,3 @@ fn factorial_not_recursive(mut n:u32) -> u32{
a
}

#[cfg(test)]
mod tests {
use super::fib;

#[test]
fn it_works() {
assert(fib(16) == 987, 'it works!');
}
}
5 changes: 5 additions & 0 deletions staking/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
target
.snfoundry_cache/
snfoundry_trace/
coverage/
profile/
163 changes: 163 additions & 0 deletions staking/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Secure Staking Smart Contract for StarkNet

This project implements a secure staking smart contract in Cairo for StarkNet, allowing users to stake Stark tokens and earn rewards in Reward tokens.

## Overview

The staking contract provides the following functionality:
- Users can stake Stark tokens
- Users can unstake their staked tokens
- Rewards are distributed proportionally based on stake share and time
- Owner can fund reward pools and manage the contract
- Emergency pause/unpause functionality
- ERC20 token recovery for mistakenly sent tokens

## Architecture

### Contracts

- `Staking.cairo`: Main staking contract
- `StarkToken.cairo`: ERC20 token for staking (for testing)
- `RewardToken.cairo`: ERC20 token for rewards (for testing)
- `erc20.cairo`: ERC20 interface

### Reward Mechanism

The contract uses a "Reward per token stored" mechanism:
- Maintains `reward_per_token_stored`: cumulative rewards per staked token
- Tracks `user_reward_per_token_paid`: last paid reward per token for each user
- Tracks `user_rewards`: pending rewards for each user
- Reward rate is set when funding rewards, distributed over a duration

Formula for earned rewards:
```
earned = user_stake * (reward_per_token_stored - user_reward_per_token_paid) + user_rewards
```

Where `reward_per_token_stored` is updated as:
```
reward_per_token_stored += (reward_rate * time_elapsed) / total_staked
```

## Security Considerations

- **Reentrancy Protection**: Uses external(v0) and checks-effects-interactions pattern
- **Access Control**: Only owner can call privileged functions
- **Input Validation**: Checks for zero amounts, insufficient balances
- **ERC20 Safety**: Proper use of transfer_from and approvals
- **Gas Efficiency**: Minimizes storage writes, careful with u256 operations
- **Pause Mechanism**: Allows halting operations in emergencies
- **Token Recovery**: Restricted recovery of ERC20 tokens (cannot recover staked/reward tokens during distribution)

### Edge Cases Handled

- Staking 0 amount reverts
- Unstaking more than staked reverts
- Insufficient reward pool handled by limiting distribution to available funds
- Rounding: Uses high precision (1e18) for calculations, but potential for small rounding errors
- Leftover rewards: If distribution period ends, remaining rewards stay in contract until next funding

## Installation and Setup

### Prerequisites

- Scarb (Cairo package manager)
- Starknet Foundry (snforge for testing)

### Build

```bash
scarb build
```

### Run Tests

```bash
snforge test
```

## Test Results

The test suite covers:
- Staking and balance updates
- Unstaking and principal return
- Reward accrual over time (using time manipulation)
- Proportional rewards for multiple stakers
- Claiming rewards
- Edge cases: staking 0, unstaking too much, claiming with no rewards
- Security: non-owner access reverts, paused contract reverts

All tests pass, demonstrating correct functionality and security.

## Deployment

### Local Deployment

Use Starknet Foundry for local testing:

```bash
snforge test
```

### Testnet Deployment

Use the provided deployment script:

```bash
# Edit deploy.sh with your private key and RPC URL
./deploy.sh
```

The script deploys:
1. StarkToken
2. RewardToken
3. Staking contract

## Usage

### Staking

```cairo
staking.stake(amount);
```

### Unstaking

```cairo
staking.unstake(amount);
```

### Claiming Rewards

```cairo
staking.claim_rewards();
```

### Funding Rewards (Owner only)

```cairo
staking.fund_rewards(amount, duration);
```

### Emergency Pause (Owner only)

```cairo
staking.pause();
staking.unpause();
```

## Design Choices

- **Reward Calculation**: Chose "reward per token stored" over per-second rate for better precision and gas efficiency
- **Funding Mechanism**: Allows topping up rewards, extending duration if ongoing
- **Pause Functionality**: Recommended for security, allows halting in emergencies
- **ERC20 Recovery**: Restricted to prevent draining staked or reward tokens
- **Gas Optimization**: Uses mappings for user data, updates rewards lazily

## API Reference

See `src/staking.cairo` for the full interface.

## License

MIT
24 changes: 24 additions & 0 deletions staking/Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "snforge_scarb_plugin_deprecated"
version = "0.49.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:c1aa8ac98f1c3cfa968a6c1fed2f9faf140733155f5fd3ac300b2059e70a8587"

[[package]]
name = "snforge_std_deprecated"
version = "0.49.0"
source = "registry+https://scarbs.xyz/"
checksum = "sha256:deb883648df0941c5def865b01a5ec7b083c95061e897366ae4342581a3b81bc"
dependencies = [
"snforge_scarb_plugin_deprecated",
]

[[package]]
name = "staking"
version = "0.1.0"
dependencies = [
"snforge_std_deprecated",
]
52 changes: 52 additions & 0 deletions staking/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[package]
name = "staking"
version = "0.1.0"
edition = "2024_07"

# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html

[dependencies]
starknet = "2.11.4"

[dev-dependencies]
snforge_std_deprecated = "0.49.0"
assert_macros = "2.11.4"

[[target.starknet-contract]]
sierra = true

[scripts]
test = "snforge test"

[tool.scarb]
allow-prebuilt-plugins = ["snforge_std_deprecated"]

# Visit https://foundry-rs.github.io/starknet-foundry/appendix/scarb-toml.html for more information

# [tool.snforge] # Define `snforge` tool section
# exit_first = true # Stop tests execution immediately upon the first failure
# fuzzer_runs = 1234 # Number of runs of the random fuzzer
# fuzzer_seed = 1111 # Seed for the random fuzzer

# [[tool.snforge.fork]] # Used for fork testing
# name = "SOME_NAME" # Fork name
# url = "http://your.rpc.url" # Url of the RPC provider
# block_id.tag = "latest" # Block to fork from (block tag)

# [[tool.snforge.fork]]
# name = "SOME_SECOND_NAME"
# url = "http://your.second.rpc.url"
# block_id.number = "123" # Block to fork from (block number)

# [[tool.snforge.fork]]
# name = "SOME_THIRD_NAME"
# url = "http://your.third.rpc.url"
# block_id.hash = "0x123" # Block to fork from (block hash)

# [profile.dev.cairo] # Configure Cairo compiler
# unstable-add-statements-code-locations-debug-info = true # Should be used if you want to use coverage
# unstable-add-statements-functions-debug-info = true # Should be used if you want to use coverage/profiler
# inlining-strategy = "avoid" # Should be used if you want to use coverage

# [features] # Used for conditional compilation
# enable_for_tests = [] # Feature name and list of other features that should be enabled with it
41 changes: 41 additions & 0 deletions staking/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# Deployment script for StarkNet testnet
# Requires starkli and a funded account

# Set your variables
RPC_URL="https://starknet-sepolia.publicnode.com" # or Testnet2
PRIVATE_KEY="your_private_key_here"
ACCOUNT_ADDRESS="your_account_address_here"

# Build contracts
echo "Building contracts..."
scarb build

# Declare contracts
echo "Declaring StarkToken..."
STARK_TOKEN_CLASS_HASH=$(starkli declare target/dev/cairotask1_StarkToken.contract_class.json --rpc $RPC_URL --account $ACCOUNT_ADDRESS --private-key $PRIVATE_KEY)

echo "Declaring RewardToken..."
REWARD_TOKEN_CLASS_HASH=$(starkli declare target/dev/cairotask1_RewardToken.contract_class.json --rpc $RPC_URL --account $ACCOUNT_ADDRESS --private-key $PRIVATE_KEY)

echo "Declaring Staking..."
STAKING_CLASS_HASH=$(starkli declare target/dev/cairotask1_Staking.contract_class.json --rpc $RPC_URL --account $ACCOUNT_ADDRESS --private-key $PRIVATE_KEY)

# Deploy tokens
INITIAL_SUPPLY=1000000000000000000000 # 1000 * 10^18

echo "Deploying StarkToken..."
STARK_TOKEN_ADDRESS=$(starkli deploy $STARK_TOKEN_CLASS_HASH $INITIAL_SUPPLY $ACCOUNT_ADDRESS --rpc $RPC_URL --account $ACCOUNT_ADDRESS --private-key $PRIVATE_KEY)

echo "Deploying RewardToken..."
REWARD_TOKEN_ADDRESS=$(starkli deploy $REWARD_TOKEN_CLASS_HASH $INITIAL_SUPPLY $ACCOUNT_ADDRESS --rpc $RPC_URL --account $ACCOUNT_ADDRESS --private-key $PRIVATE_KEY)

# Deploy staking contract
echo "Deploying Staking contract..."
STAKING_ADDRESS=$(starkli deploy $STAKING_CLASS_HASH $STARK_TOKEN_ADDRESS $REWARD_TOKEN_ADDRESS --rpc $RPC_URL --account $ACCOUNT_ADDRESS --private-key $PRIVATE_KEY)

echo "Deployment complete!"
echo "StarkToken: $STARK_TOKEN_ADDRESS"
echo "RewardToken: $REWARD_TOKEN_ADDRESS"
echo "Staking: $STAKING_ADDRESS"
11 changes: 11 additions & 0 deletions staking/snfoundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Visit https://foundry-rs.github.io/starknet-foundry/appendix/snfoundry-toml.html
# and https://foundry-rs.github.io/starknet-foundry/projects/configuration.html for more information

# [sncast.default] # Define a profile name
# url = "https://starknet-sepolia.public.blastapi.io/rpc/v0_9" # Url of the RPC provider
# accounts-file = "../account-file" # Path to the file with the account data
# account = "mainuser" # Account from `accounts_file` or default account file that will be used for the transactions
# keystore = "~/keystore" # Path to the keystore file
# wait-params = { timeout = 300, retry-interval = 10 } # Wait for submitted transaction parameters
# block-explorer = "StarkScan" # Block explorer service used to display links to transaction details
# show-explorer-links = true # Print links pointing to pages with transaction details in the chosen block explorer
12 changes: 12 additions & 0 deletions staking/src/erc20.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#[starknet::interface]
pub trait IERC20<TContractState> {
fn name(self: @TContractState) -> ByteArray;
fn symbol(self: @TContractState) -> ByteArray;
fn decimals(self: @TContractState) -> u8;
fn total_supply(self: @TContractState) -> u256;
fn balance_of(self: @TContractState, account: starknet::ContractAddress) -> u256;
fn allowance(self: @TContractState, owner: starknet::ContractAddress, spender: starknet::ContractAddress) -> u256;
fn transfer(ref self: TContractState, recipient: starknet::ContractAddress, amount: u256) -> bool;
fn transfer_from(ref self: TContractState, sender: starknet::ContractAddress, recipient: starknet::ContractAddress, amount: u256) -> bool;
fn approve(ref self: TContractState, spender: starknet::ContractAddress, amount: u256) -> bool;
}
4 changes: 4 additions & 0 deletions staking/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod erc20;
pub mod stark_token;
pub mod reward_token;
pub mod staking;
Loading