Skip to content

Commit

Permalink
staking (#290)
Browse files Browse the repository at this point in the history
* Init: staking component

* WIP: added more function signatures in the IStaking interface

* created event enum and enum variant structs

* implemented initializer() to be used in constructors

* implemented read functions

* WIP: created helper functions in the InternalImpl

* FEAT: staking component implemented

* MOD: added custom Errors

* LINTING: scarb fmt

* MOD: emitted event for stake(), withdraw() and get_reward() functions

* MOD: modified notify_reward_amount() and added transfer_from()

* FIXESL fixed get_reward() impl to update rewards before calculating user rewards

* MOD: changed get_reward() to claim_reward() for clarity.

* FEAT: implemented staking_reward contract to use StakingComponent

* TEST: completed test setup for staking contract and component

* TEST: implemented test_notify_reward_amount() and test_stake()

* TEST: implemented test_stake(), test_rewards_earned(), test_claim_reward() and test_withdraw()

* MOD: created interfaces.cairo and modified imports.

* MOD: uncommented some test mods

* MOD: added requested review changes

* pulled from main

* merge main
  • Loading branch information
casweeney authored Dec 2, 2024
1 parent b79cfba commit f8dc60d
Show file tree
Hide file tree
Showing 11 changed files with 945 additions and 16 deletions.
28 changes: 14 additions & 14 deletions onchain/cairo/.snfoundry_cache/.prev_tests_failed
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
afk::tests::launchpad_tests::launchpad_tests::launchpad_test_calculation
afk::tests::launchpad_tests::launchpad_tests::test_get_coin_amount_by_quote_amount_for_buy_steps
afk::tests::launchpad_tests::launchpad_tests::test_get_coin_launch
afk::tests::launchpad_tests::launchpad_tests::test_get_share_key_of_user
afk::tests::launchpad_tests::launchpad_tests::test_launch_token
afk::tests::launchpad_tests::launchpad_tests::test_launch_token_with_uncreated_token
afk::tests::launchpad_tests::launchpad_tests::test_sell_coin_when_quote_amount_is_greater_than_liquidity_raised
afk::tests::launchpad_tests::launchpad_tests::test_sell_coin_when_share_too_low
afk::tests::launchpad_tests::launchpad_tests::test_set_protocol_fee_percent_non_admin
afk::tests::launchpad_tests::launchpad_tests::test_buy_coin_with_different_supply
afk::tests::launchpad_tests::launchpad_tests::launchpad_integration
afk::tests::launchpad_tests::launchpad_tests::launchpad_buy_all
afk::tests::launchpad_tests::launchpad_tests::test_launchpad_end_to_end
afk::tests::launchpad_tests::launchpad_tests::launchpad_end_to_end
afk::tests::launchpad_tests::launchpad_tests::launchpad_test_calculation
afk::tests::launchpad_tests::launchpad_tests::test_get_coin_amount_by_quote_amount_for_buy_steps
afk::tests::launchpad_tests::launchpad_tests::test_get_coin_launch
afk::tests::launchpad_tests::launchpad_tests::test_get_share_key_of_user
afk::tests::launchpad_tests::launchpad_tests::test_launch_token
afk::tests::launchpad_tests::launchpad_tests::test_launch_token_with_uncreated_token
afk::tests::launchpad_tests::launchpad_tests::test_sell_coin_when_quote_amount_is_greater_than_liquidity_raised
afk::tests::launchpad_tests::launchpad_tests::test_sell_coin_when_share_too_low
afk::tests::launchpad_tests::launchpad_tests::test_set_protocol_fee_percent_non_admin
afk::tests::launchpad_tests::launchpad_tests::test_buy_coin_with_different_supply
afk::tests::launchpad_tests::launchpad_tests::launchpad_integration
afk::tests::launchpad_tests::launchpad_tests::launchpad_buy_all
afk::tests::launchpad_tests::launchpad_tests::test_launchpad_end_to_end
afk::tests::launchpad_tests::launchpad_tests::launchpad_end_to_end
1 change: 1 addition & 0 deletions onchain/cairo/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scarb 2.8.5
5 changes: 4 additions & 1 deletion onchain/cairo/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub mod keys;
pub mod math;
pub mod sha256;
pub mod social;

pub mod staking;

pub mod utils;

pub mod launchpad {
Expand Down Expand Up @@ -122,5 +125,5 @@ pub mod tests {
pub mod tap_tests;
pub mod utils;
pub mod vault_tests;
pub mod staking_tests;
}

3 changes: 3 additions & 0 deletions onchain/cairo/src/staking.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod staking;
pub mod mocks;
pub mod interfaces;
46 changes: 46 additions & 0 deletions onchain/cairo/src/staking/interfaces.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use starknet::ContractAddress;

#[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: ContractAddress) -> u256;
fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256;

fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool;
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool;

fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool;
}

#[starknet::interface]
pub trait IStaking<TContractState> {
fn set_rewards_duration(ref self: TContractState, duration: u256);
fn notify_reward_amount(ref self: TContractState, amount: u256);
fn stake(ref self: TContractState, amount: u256);
fn withdraw(ref self: TContractState, amount: u256);
fn claim_reward(ref self: TContractState);

fn last_time_reward_applicable(self: @TContractState) -> u256;
fn reward_per_token(self: @TContractState) -> u256;
fn rewards_earned(self: @TContractState, account: ContractAddress) -> u256;

fn staking_token(self: @TContractState) -> ContractAddress;
fn rewards_token(self: @TContractState) -> ContractAddress;
fn duration(self: @TContractState) -> u256;
fn finish_at(self: @TContractState) -> u256;
fn updated_at(self: @TContractState) -> u256;
fn reward_rate(self: @TContractState) -> u256;
fn reward_per_token_stored(self: @TContractState) -> u256;
fn user_reward_per_token_paid(self: @TContractState, user: ContractAddress) -> u256;
fn rewards(self: @TContractState, user: ContractAddress) -> u256;
fn total_supply(self: @TContractState) -> u256;
fn balance_of(self: @TContractState, user: ContractAddress) -> u256;
fn owner(self: @TContractState) -> ContractAddress;

fn return_block_timestamp(self: @TContractState) -> u256;
}
2 changes: 2 additions & 0 deletions onchain/cairo/src/staking/mocks.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod staking_rewards;
pub mod mock_erc20;
144 changes: 144 additions & 0 deletions onchain/cairo/src/staking/mocks/mock_erc20.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#[starknet::contract]
pub mod MockToken {
use starknet::event::EventEmitter;
use starknet::{ContractAddress, get_caller_address};
use core::starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess, Map, StoragePathEntry};
use crate::staking::interfaces::IERC20;
use core::num::traits::Zero;

#[storage]
pub struct Storage {
balances: Map<ContractAddress, u256>,
allowances: Map<(ContractAddress, ContractAddress), u256>, // Mapping<(owner, spender), amount>
token_name: ByteArray,
symbol: ByteArray,
decimal: u8,
total_supply: u256,
owner: ContractAddress,
}

#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
Transfer: Transfer,
Approval: Approval,
}

#[derive(Drop, starknet::Event)]
pub struct Transfer {
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
amount: u256,
}

#[derive(Drop, starknet::Event)]
pub struct Approval {
#[key]
owner: ContractAddress,
#[key]
spender: ContractAddress,
value: u256
}

#[constructor]
fn constructor(ref self: ContractState) {
self.token_name.write("Staking Token");
self.symbol.write("SKT");
self.decimal.write(18);
self.owner.write(get_caller_address());
}

#[abi(embed_v0)]
impl MockTokenImpl of IERC20<ContractState> {
fn total_supply(self: @ContractState) -> u256 {
self.total_supply.read()
}

fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {
let balance = self.balances.entry(account).read();

balance
}

fn allowance(self: @ContractState, owner: ContractAddress, spender: ContractAddress) -> u256 {
let allowance = self.allowances.entry((owner, spender)).read();

allowance
}

fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
let sender = get_caller_address();

let sender_prev_balance = self.balances.entry(sender).read();
let recipient_prev_balance = self.balances.entry(recipient).read();

assert(sender_prev_balance >= amount, 'Insufficient amount');

self.balances.entry(sender).write(sender_prev_balance - amount);
self.balances.entry(recipient).write(recipient_prev_balance + amount);

assert(self.balances.entry(recipient).read() > recipient_prev_balance, 'Transaction failed');

self.emit(Transfer { from: sender, to: recipient, amount });

true
}

fn transfer_from(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool {
let spender = get_caller_address();

let spender_allowance = self.allowances.entry((sender, spender)).read();
let sender_balance = self.balances.entry(sender).read();
let recipient_balance = self.balances.entry(recipient).read();

assert(amount <= spender_allowance, 'amount exceeds allowance');
assert(amount <= sender_balance, 'amount exceeds balance');

self.allowances.entry((sender, spender)).write(spender_allowance - amount);
self.balances.entry(sender).write(sender_balance - amount);
self.balances.entry(recipient).write(recipient_balance + amount);

self.emit(Transfer { from: sender, to: recipient, amount });

true
}

fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool {
let caller = get_caller_address();

self.allowances.entry((caller, spender)).write(amount);

self.emit(Approval { owner: caller, spender, value: amount });

true
}

fn name(self: @ContractState) -> ByteArray {
self.token_name.read()
}

fn symbol(self: @ContractState) -> ByteArray {
self.symbol.read()
}

fn decimals(self: @ContractState) -> u8 {
self.decimal.read()
}

fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
let previous_total_supply = self.total_supply.read();
let previous_balance = self.balances.entry(recipient).read();

self.total_supply.write(previous_total_supply + amount);
self.balances.entry(recipient).write(previous_balance + amount);

let zero_address = Zero::zero();

self.emit(Transfer { from: zero_address, to: recipient, amount });

true
}
}
}
34 changes: 34 additions & 0 deletions onchain/cairo/src/staking/mocks/staking_rewards.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#[starknet::contract]
mod StakingRewards {
use crate::staking::staking::StakingComponent;
use starknet::ContractAddress;

component!(path: StakingComponent, storage: staking, event: StakingEvent);

#[storage]
struct Storage {
#[substorage(v0)]
staking: StakingComponent::Storage,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
StakingEvent: StakingComponent::Event,
}

#[constructor]
fn constructor(
ref self: ContractState,
owner: ContractAddress,
staking_token: ContractAddress,
reward_token: ContractAddress
) {
self.staking._initializer(owner, staking_token, reward_token);
}

#[abi(embed_v0)]
impl StakingImpl = StakingComponent::StakingImpl<ContractState>;
impl StakingInternalImpl = StakingComponent::InternalImpl<ContractState>;
}
Loading

0 comments on commit f8dc60d

Please sign in to comment.