gauthami vijay
gauthami vijay

Reputation: 61

Consider adding a `main` function to `staking_exchange/lib.rs

I have drafted a RUST contract for staking and unstaking in chain like POLKADOT. I am using substrate, ink , cargo to do this . lib.rs :

#![cfg_attr(not(feature = "std"), no_std)]

use ink::prelude::vec::Vec;
use ink::storage::traits::StorageLayout;
use ink::env::DefaultEnvironment;
type AccountId = <DefaultEnvironment as ink::env::Environment>::AccountId;
use ink::storage::Mapping;
use parity_scale_codec::{Encode, Decode};
use scale_info::TypeInfo;

#[ink::contract]
mod staking_exchange {
    use super::*;

    #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, StorageLayout)]
    #[cfg_attr(feature = "std", derive(TypeInfo))]
    pub struct InterestRateChange {
        pub rate: u64,
        pub start_time: u64,
    }

    #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, StorageLayout)]
    #[cfg_attr(feature = "std", derive(TypeInfo))]
    pub struct StakeInfo {
        pub amount: u64,
        pub start_time: u64,
        pub interest_accrued: u64,
    }

    #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, StorageLayout)]
    #[cfg_attr(feature = "std", derive(TypeInfo))]
    pub struct UnstakeRequest {
        pub amount: u64,
        pub request_time: u64,
        pub processed: bool,
    }

    #[ink(event)]
    pub struct Unstaked {
        #[ink(topic)]
        pub user: AccountId,
        pub principal: u64,
        pub interest: u64,
        pub timestamp: u64,
    }


    #[ink(storage)]
    pub struct StakingExchange {
        pub interest_rate_history: Vec<InterestRateChange>,
        user_stakes: Mapping<AccountId, Vec<StakeInfo>>,
        pub unstake_requests: Mapping<AccountId, Vec<UnstakeRequest>>,
        pub cooling_period: u64,
        pub fee_percentage: u64,
        treasury: AccountId,
    }

    impl StakingExchange {
        #[ink(constructor)]
        pub fn new(treasury: AccountId, cooling_period: u64, fee_percentage: u64) -> Self {
            let interest_rate_change = InterestRateChange {
                rate: 50000000000000000, // 5% interest rate
                start_time: Self::env().block_timestamp(),
            };

            let mut interest_rate_history = Vec::new();
            interest_rate_history.push(interest_rate_change);

            Self {
                interest_rate_history,
                user_stakes: Default::default(),
                unstake_requests: Default::default(),
                cooling_period,
                fee_percentage,
                treasury,
            }
        }

        #[ink(message, payable)]
        pub fn stake(&mut self) {
            let caller = self.env().caller();
            let amount: u64 = self.env().transferred_value().try_into().unwrap();
            assert!(amount > 0, "Cannot stake 0");

            let mut stakes = self.user_stakes.get(&caller).unwrap_or(Vec::new());
            stakes.push(StakeInfo {
                amount,
                start_time: self.env().block_timestamp(),
                interest_accrued: 0,
            });
            self.user_stakes.insert(caller, &stakes);

            self.env().emit_event(Staked {
                user: caller,
                amount,
                timestamp: self.env().block_timestamp(),
            });
        }

        #[ink(message)]
        pub fn request_unstake(&mut self, amount: u64) {
            let caller = self.env().caller();
            let total_staked = self.get_total_staked(caller);
            assert!(amount <= total_staked, "Insufficient staked amount");

            let mut unstake_requests = self.unstake_requests.get(&caller).unwrap_or(Vec::new());
            unstake_requests.push(UnstakeRequest {
                amount,
                request_time: self.env().block_timestamp(),
                processed: false,
            });
            self.unstake_requests.insert(caller, &unstake_requests);

            let unstake_len = unstake_requests.len() as u64;
            let request_id = unstake_len.checked_sub(1).expect("Underflow in request ID subtraction");

            self.env().emit_event(UnstakeRequested {
            user: caller,
            amount,
            request_id: request_id,
            timestamp: self.env().block_timestamp(),
         });

        }

        #[ink(message)]

pub fn complete_unstake(&mut self, request_index: u64) {
    let caller = self.env().caller();
    let mut unstake_requests = self.unstake_requests.get(&caller).unwrap_or(Vec::new());

    // Ensure the request index is valid
    assert!((request_index as usize) < unstake_requests.len(), "Invalid request index");

    let request = &mut unstake_requests[request_index as usize];

    // Ensure the unstake request is not processed
    assert!(!request.processed, "Request already processed");

    // Ensure the cooling period has passed
    let safe_timestamp = request.request_time.checked_add(self.cooling_period)
    .expect("Overflow in block timestamp addition");
    assert!(self.env().block_timestamp() >= safe_timestamp, "Cooling period not yet passed");


    let amount = request.amount;

    // Check the total staked amount for the user
    let total_staked = self.get_total_staked(caller);
    assert!(total_staked >= amount, "Insufficient staked amount");

    // Process the unstake (calculating principal and interest)
    let (total_principal, total_interest) = self.process_unstake(caller, amount);

    // Calculate the fee (based on the interest) and final payout
    let scaled_fee = 1e18 as u64;
    let treasury_fee = total_interest
        .checked_mul(self.fee_percentage)
        .expect("Overflow in treasury fee multiplication")
        .checked_div(scaled_fee)
       .expect("Division error in treasury fee");
  // Assuming fee_percentage is in similar scale
    let interest_after_fee = total_interest
        .checked_sub(treasury_fee)
        .expect("Underflow in interest fee calculation");


    let total_amount = total_principal
        .checked_add(interest_after_fee)
        .expect("Overflow in total amount calculation");


    // Ensure the contract has enough balance to cover the unstake
    let safe_total = total_amount
        .checked_add(treasury_fee)
        .expect("Overflow in total balance calculation");
    assert!(self.env().balance() >= safe_total as u128, "Contract does not have enough balance");


    // Transfer the treasury fee to the treasury account
    self.env().transfer(self.treasury, treasury_fee as u128)
        .expect("Treasury transfer failed");

    // Transfer the remaining amount (principal + interest after fee) to the user
    self.env().transfer(caller, total_amount as u128)
        .expect("Transfer to user failed");

    // Emit the Unstaked event
    self.env().emit_event(Unstaked {
        user: caller,
        principal: total_principal,
        interest: total_interest,
        timestamp: self.env().block_timestamp(),
    });

     // Mark the request as processed
     request.processed = true;

      // Update the unstake_requests mapping

       self.unstake_requests.insert(caller, &unstake_requests);
    }

   fn process_unstake(&mut self, user: AccountId, amount: u64) -> (u64, u64) {
        let mut remaining_amount = amount;
        let mut total_principal: u64 = 0;
        let mut total_interest: u64 = 0;

        let mut stakes = self.user_stakes.get(&user).unwrap_or(Vec::new());

        for stake_info in stakes.iter_mut() {
            let stake_amount = stake_info.amount;
            if stake_amount > 0 {
                let accrued_interest = self.calculate_interest(stake_info);

                if stake_amount >= remaining_amount {
                    let interest_for_unstaked = accrued_interest.checked_mul(remaining_amount)
                        .expect("Overflow detected")
                        .checked_div(stake_amount)
                        .expect("Division error");

                    total_interest = total_interest.checked_add(interest_for_unstaked)
                        .expect("Overflow detected");
                    total_principal = total_principal.checked_add(remaining_amount)
                        .expect("Overflow detected");

                    stake_info.amount = stake_info.amount.checked_sub(remaining_amount)
                        .expect("Underflow detected");


                    if stake_info.amount == 0 {
                        stake_info.interest_accrued = 0;
                    } else {
                        stake_info.interest_accrued = stake_info.interest_accrued.checked_add(accrued_interest)
                        .expect("Overflow detected")
                        .checked_sub(interest_for_unstaked)
                        .expect("Underflow detected");
                        stake_info.start_time = self.env().block_timestamp();
                    }
                   // remaining_amount = 0;
                    break;
                } else {
                    total_interest = total_interest.checked_add(accrued_interest)
                    .expect("Overflow detected");
                    total_principal = total_principal.checked_add(stake_amount)
                    .expect("Overflow detected");

                remaining_amount = remaining_amount.checked_sub(stake_amount)
                    .expect("Underflow detected");


                    stake_info.amount = 0;
                    stake_info.interest_accrued = 0;
                }
            }
        }

        self.user_stakes.insert(user, &stakes);
        (total_principal, total_interest)
    }

    fn calculate_interest(&self, stake_info: &StakeInfo) -> u64 {
        let mut total_interest: u64 = 0;
        let mut last_time = stake_info.start_time;
        let amount = stake_info.amount;
        let history_length = self.interest_rate_history.len();

        for i in 1..history_length {
            let rate_change = &self.interest_rate_history[i];
            if rate_change.start_time > last_time {
                let time_period = rate_change.start_time.checked_sub(last_time)
                .expect("Underflow in time period calculation");

                #[allow(clippy::arithmetic_side_effects)]
                let interest_for_period = amount
                        .checked_mul(self.interest_rate_history[i - 1].rate)
                        .expect("Overflow in multiplication of amount and rate")
                        .checked_mul(time_period)
                        .expect("Overflow in multiplication of result and time period")
                        .checked_div(365 * 24 * 60 * 60 * 1_000_000_000)
                        .expect("Division error in calculating interest for period");


                total_interest = total_interest
                        .checked_add(interest_for_period)
                        .expect("Overflow in total interest calculation");


                last_time = rate_change.start_time;
            }
        }

        if last_time < self.env().block_timestamp() {
            let time_period = self.env().block_timestamp().checked_sub(last_time)
                        .expect("Underflow in block timestamp subtraction");

            let rate = self.interest_rate_history.get(history_length.checked_sub(1)
                        .expect("History length underflow"))
                        .expect("Index out of bounds in interest rate history")
                        .rate;



            let interest_for_period = (amount as u64)
                .checked_mul(rate)
                .expect("Overflow in multiplication")
                .checked_mul(time_period as u64)
                .expect("Overflow in time period multiplication")
                .checked_div(365 * 24 * 60 * 60 * 1_000_000_000)
                .expect("Division error");

            total_interest = total_interest
                .checked_add(interest_for_period)
                .expect("Overflow in interest calculation");
        }

        total_interest
    }
        #[ink(message)]
pub fn claim_reward(&mut self) {
    let caller = self.env().caller();
    let mut stakes = self.user_stakes.get(&caller).unwrap_or(Vec::new());

    // Calculate total accrued interest for all stakes
    let mut total_accrued_interest: u64 = 0;

    for stake_info in stakes.iter_mut() {
        let accrued_interest = self.calculate_interest(stake_info);
        stake_info.interest_accrued = stake_info.interest_accrued
                .checked_add(accrued_interest)
                .expect("Overflow in interest accrued calculation");

        total_accrued_interest = total_accrued_interest
                .checked_add(accrued_interest)
                .expect("Overflow in total accrued interest calculation");

        // Update the stake's start time to the current time
        stake_info.start_time = self.env().block_timestamp();
    }

    // Ensure there is enough balance in the contract to pay the rewards
    assert!(
        self.env().balance() >= total_accrued_interest.into(),
        "Contract does not have enough balance to pay rewards"
    );

    // Transfer the rewards (interest accrued) to the user
    self.env().transfer(caller, total_accrued_interest.into())
        .expect("Transfer of rewards failed");

    // Save the updated stakes back to storage
    self.user_stakes.insert(caller, &stakes);
}


        #[ink(message)]
        pub fn get_total_staked(&self, user: AccountId) -> u64 {
            let stakes = self.user_stakes.get(&user).unwrap_or(Vec::new());
            stakes.iter().map(|stake| stake.amount).sum()
        }
    }

    #[ink(event)]
    pub struct Staked {
        #[ink(topic)]
        pub user: AccountId,
        pub amount: u64,
        pub timestamp: u64,
    }

    #[ink(event)]
    pub struct UnstakeRequested {
        #[ink(topic)]
        pub user: AccountId,
        pub amount: u64,
        pub request_id: u64,
        pub timestamp: u64,
    }
}


#[cfg(test)]
mod tests {
    use super::*;
    use crate::staking_exchange::{StakingExchange, Staked, UnstakeRequested};

    #[ink::test]
    fn test_initialize_contract() {
        // Initialize the contract
        let mut contract = StakingExchange::new(
            AccountId::from([0x1; 32]),
            10, // cooling period
            10000000000000000, // fee percentage (1%)
        );

        // Check initial values
        assert_eq!(contract.cooling_period, 10);
        assert_eq!(contract.fee_percentage, 10000000000000000);
        assert_eq!(contract.interest_rate_history.len(), 1);
        assert_eq!(contract.interest_rate_history[0].rate, 50000000000000000);
    }

    #[ink::test]
    fn test_stake() {
        let accounts = ink::env::test::default_accounts::<ink::env::DefaultEnvironment>();
        let mut contract = StakingExchange::new(
                accounts.alice,
                10, // cooling period
                10000000000000000, // fee percentage (1%)
        );

        // Mock the environment to send a transferred value
        ink::env::test::set_value_transferred::<ink::env::DefaultEnvironment>(1000);

        // Alice stakes 1000 units
        contract.stake();

        // Verify that the stake was added
        let total_staked = contract.get_total_staked(accounts.alice);
        assert_eq!(total_staked, 1000);

        // Verify that the `Staked` event was emitted
        let emitted_events = ink::env::test::recorded_events().collect::<Vec<_>>();
        assert_eq!(emitted_events.len(), 1);

        // Extract the event data
        let emitted_event = &emitted_events[0];
        let event_data = emitted_event.data;
        let decoded_event: Staked = <Staked as parity_scale_codec::Decode>::decode(&mut &event_data[..])
        .expect("Failed to decode event");

        // Check the decoded event data
        assert_eq!(decoded_event.user, accounts.alice);
        assert_eq!(decoded_event.amount, 1000);
        }


    #[ink::test]
fn test_request_unstake() {
    let accounts = ink::env::test::default_accounts::<ink::env::DefaultEnvironment>();
    let mut contract = StakingExchange::new(
        accounts.alice,
        10, // cooling period
        10000000000000000, // fee percentage (1%)
    );

    // Mock the environment to send a transferred value
    ink::env::test::set_value_transferred::<ink::env::DefaultEnvironment>(1000);

    // Alice stakes 1000 units
    contract.stake();

    // Alice requests to unstake 500 units
    contract.request_unstake(500);

    // Verify that an unstake request was created
    let unstake_requests = contract.unstake_requests.get(&accounts.alice).unwrap();
    assert_eq!(unstake_requests.len(), 1);
    assert_eq!(unstake_requests[0].amount, 500);
    assert_eq!(unstake_requests[0].processed, false);

    // Verify that the `UnstakeRequested` event was emitted
    let emitted_events = ink::env::test::recorded_events().collect::<Vec<_>>();
    assert_eq!(emitted_events.len(), 2); // Staked + UnstakeRequested

    // Access the second emitted event (UnstakeRequested event)
    let emitted_event = &emitted_events[1];

    // Clone the data explicitly before decoding
    let event_data: &[u8] = emitted_event.data.as_slice(); // Convert to slice directly

    // Manually decode the event data using UnstakeRequested
    let decoded_event: UnstakeRequested = <UnstakeRequested as parity_scale_codec::Decode>::decode(&mut &event_data[..])
        .expect("Failed to decode event");

    // Check the decoded event data
    assert_eq!(decoded_event.user, accounts.alice);
    assert_eq!(decoded_event.amount, 500);
}


    #[ink::test]
    fn test_complete_unstake() {
        let accounts = ink::env::test::default_accounts::<ink::env::DefaultEnvironment>();
        let mut contract = StakingExchange::new(
            accounts.alice,
            10, // cooling period
            10000000000000000, // fee percentage (1%)
        );

        // Mock the environment to send a transferred value
        ink::env::test::set_value_transferred::<ink::env::DefaultEnvironment>(1000);

        // Alice stakes 1000 units
        contract.stake();

        // Alice requests to unstake 500 units
        contract.request_unstake(500);

        // Simulate the passing of the cooling period
        ink::env::test::advance_block::<ink::env::DefaultEnvironment>();

        // Alice completes the unstake
        contract.complete_unstake(0);

        // Verify that the unstake request was processed
        let unstake_requests = contract.unstake_requests.get(&accounts.alice).unwrap();
        assert!(unstake_requests[0].processed);

        // Verify that the stake amount was reduced
        let total_staked = contract.get_total_staked(accounts.alice);
        assert_eq!(total_staked, 500);
    }
}

This is my contract with test

Cargo.toml:

[package]
name = "staking_exchange"
version = "0.1.0"
authors = ["Gauthami <[email protected]>"]
edition = "2021"

[dependencies]
ink = { version = "5.0.0", default-features = false }
ink_storage = { version = "5.0.0", default-features = false }
parity-scale-codec = { version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true }

[dev-dependencies]
ink_e2e = { version = "5.0.0" }

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
    "ink/std",
    "ink_storage/std",
    "parity-scale-codec/std",
    "scale-info/std"
]
ink-as-dependency = []
e2e-tests = []

this is my Cargo.toml .when I try to build it with cargo contract build I get

error[E0601]: `main` function not found in crate `staking_exchange`
   --> /home/babylontest/staking_exchange/lib.rs:515:2
    |
515 | }
    |  ^ consider adding a `main` function to `/home/babylontest/staking_exchange/lib.rs`

Some errors have detailed explanations: E0404, E0432, E0433, E0601.
For more information about an error, try `rustc --explain E0404`.
warning: `staking_exchange` (bin "staking_exchange") generated 2 warnings
error: could not compile `staking_exchange` (bin "staking_exchange") due to 11 previous errors; 2 warnings emitted
ERROR:

I tried adding crate-type = ["cdylib", "rlib"] in [lib].I get

error: failed to parse manifest at `/tmp/cargo-contract_BTJ3XW/Cargo.toml`

Caused by:
  the target `staking_exchange` is a binary and can't have any crate-types set (currently "cdylib, rlib")
ERROR:

Upvotes: 0

Views: 29

Answers (0)

Related Questions