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
15 changes: 15 additions & 0 deletions contracts/insurance/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use soroban_sdk::contracterror;

/// Insurance module errors
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum InsuranceError {
AlreadyInitialized = 500,
UserNotInsured = 501,
ClaimNotFound = 502,
ClaimAlreadyProcessed = 503,
ClaimNotVerified = 504,
}

/// Result type alias for insurance operations
pub type InsuranceResult<T> = core::result::Result<T, InsuranceError>;
34 changes: 19 additions & 15 deletions contracts/insurance/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![no_std]

use crate::errors::InsuranceError;
use soroban_sdk::{contract, contractimpl, contracttype, token, Address, Env};

#[contracttype]
Expand Down Expand Up @@ -44,9 +45,9 @@ impl InsurancePool {
oracle: Address,
premium_amount: i128,
payout_amount: i128,
) {
) -> Result<(), InsuranceError> {
if env.storage().instance().has(&DataKey::Admin) {
panic!("Already initialized");
return Err(InsuranceError::AlreadyInitialized);
}
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::Token, &token);
Expand All @@ -58,6 +59,8 @@ impl InsurancePool {
.instance()
.set(&DataKey::PayoutAmount, &payout_amount);
env.storage().instance().set(&DataKey::ClaimCount, &0u64);

Ok(())
}

pub fn pay_premium(env: Env, user: Address) {
Expand All @@ -78,7 +81,7 @@ impl InsurancePool {
.set(&DataKey::IsInsured(user.clone()), &true);
}

pub fn file_claim(env: Env, user: Address, course_id: u64) -> u64 {
pub fn file_claim(env: Env, user: Address, course_id: u64) -> Result<u64, InsuranceError> {
user.require_auth();

if !env
Expand All @@ -87,7 +90,7 @@ impl InsurancePool {
.get::<_, bool>(&DataKey::IsInsured(user.clone()))
.unwrap_or(false)
{
panic!("User is not insured");
return Err(InsuranceError::UserNotInsured);
}

let mut claim_count = env
Expand All @@ -110,21 +113,21 @@ impl InsurancePool {
.instance()
.set(&DataKey::ClaimCount, &claim_count);

claim_count
Ok(claim_count)
}

pub fn process_claim(env: Env, claim_id: u64, result: bool) {
pub fn process_claim(env: Env, claim_id: u64, result: bool) -> Result<(), InsuranceError> {
let oracle = env.storage().instance().get::<_, Address>(&DataKey::Oracle).unwrap();
oracle.require_auth();

let mut claim = env
.storage()
.instance()
.get::<_, Claim>(&DataKey::Claim(claim_id))
.expect("Claim not found");
.ok_or(InsuranceError::ClaimNotFound)?;

if claim.status != ClaimStatus::Pending {
panic!("Claim already processed");
return Err(InsuranceError::ClaimAlreadyProcessed);
}

if result {
Expand All @@ -136,17 +139,19 @@ impl InsurancePool {
env.storage()
.instance()
.set(&DataKey::Claim(claim_id), &claim);

Ok(())
}

pub fn payout(env: Env, claim_id: u64) {
pub fn payout(env: Env, claim_id: u64) -> Result<(), InsuranceError> {
let mut claim = env
.storage()
.instance()
.get::<_, Claim>(&DataKey::Claim(claim_id))
.expect("Claim not found");
.ok_or(InsuranceError::ClaimNotFound)?;

if claim.status != ClaimStatus::Verified {
panic!("Claim not verified");
return Err(InsuranceError::ClaimNotVerified);
}

let token_addr = env.storage().instance().get::<_, Address>(&DataKey::Token).unwrap();
Expand All @@ -164,13 +169,12 @@ impl InsurancePool {
.instance()
.set(&DataKey::Claim(claim_id), &claim);

// Remove insurance after payout? Or keep it?
// Usually insurance is for a specific term or event.
// Here we assume it's one-time use per premium for simplicity, or maybe not.
// Let's remove insurance to prevent multiple claims for one premium if that's the model.
// But the prompt says "protects against course completion failures".
// Let's assume one premium covers one claim for now.
env.storage().instance().remove(&DataKey::IsInsured(claim.user));

Ok(())
}

pub fn withdraw(env: Env, amount: i128) {
Expand All @@ -193,4 +197,4 @@ impl InsurancePool {
}
}

mod test;
mod errors;
47 changes: 29 additions & 18 deletions contracts/teachlink/src/bridge.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::events::{BridgeCompletedEvent, BridgeInitiatedEvent, DepositEvent, ReleaseEvent};
use crate::errors::BridgeError;
use crate::storage::{
ADMIN, BRIDGE_FEE, BRIDGE_TXS, FEE_RECIPIENT, MIN_VALIDATORS, NONCE, SUPPORTED_CHAINS, TOKEN,
VALIDATORS,
Expand All @@ -19,10 +20,10 @@ impl Bridge {
admin: Address,
min_validators: u32,
fee_recipient: Address,
) {
) -> Result<(), BridgeError> {
// Check if already initialized
if env.storage().instance().has(&TOKEN) {
panic!("Contract already initialized");
return Err(BridgeError::AlreadyInitialized);
}

env.storage().instance().set(&TOKEN, &token);
Expand All @@ -41,6 +42,8 @@ impl Bridge {
// Initialize empty supported chains map
let chains: Map<u32, bool> = Map::new(env);
env.storage().instance().set(&SUPPORTED_CHAINS, &chains);

Ok(())
}

/// Bridge tokens out to another chain (lock/burn tokens on Stellar)
Expand All @@ -53,12 +56,12 @@ impl Bridge {
amount: i128,
destination_chain: u32,
destination_address: soroban_sdk::Bytes,
) -> u64 {
) -> Result<u64, BridgeError> {
from.require_auth();

// Validate inputs
if amount <= 0 {
panic!("Amount must be positive");
return Err(BridgeError::AmountMustBePositive);
}

// Check if destination chain is supported
Expand All @@ -68,7 +71,7 @@ impl Bridge {
.get(&SUPPORTED_CHAINS)
.unwrap_or_else(|| Map::new(env));
if !supported_chains.get(destination_chain).unwrap_or(false) {
panic!("Destination chain not supported");
return Err(BridgeError::DestinationChainNotSupported);
}

// Get token address
Expand Down Expand Up @@ -146,7 +149,7 @@ impl Bridge {
}
.publish(env);

nonce
Ok(nonce)
}

/// Complete a bridge transaction (mint/release tokens on Stellar)
Expand All @@ -157,18 +160,18 @@ impl Bridge {
env: &Env,
message: CrossChainMessage,
validator_signatures: Vec<Address>,
) {
) -> Result<(), BridgeError> {
// Validate that we have enough validator signatures
let min_validators: u32 = env.storage().instance().get(&MIN_VALIDATORS).unwrap();
if (validator_signatures.len() as u32) < min_validators {
panic!("Insufficient validator signatures");
return Err(BridgeError::InsufficientValidatorSignatures);
}

// Verify all signatures are from valid validators
let validators: Map<Address, bool> = env.storage().instance().get(&VALIDATORS).unwrap();
for validator in validator_signatures.iter() {
if !validators.get(validator.clone()).unwrap_or(false) {
panic!("Invalid validator signature");
return Err(BridgeError::InvalidValidatorSignature);
}
}

Expand All @@ -179,7 +182,7 @@ impl Bridge {
.get(&NONCE)
.unwrap_or_else(|| Map::new(env));
if processed_nonces.get(message.nonce).unwrap_or(false) {
panic!("Nonce already processed");
return Err(BridgeError::NonceAlreadyProcessed);
}
processed_nonces.set(message.nonce, true);
env.storage().persistent().set(&NONCE, &processed_nonces);
Expand All @@ -189,7 +192,7 @@ impl Bridge {

// Verify token matches
if message.token != token {
panic!("Token mismatch");
return Err(BridgeError::TokenMismatch);
}

// Mint/release tokens to recipient
Expand Down Expand Up @@ -217,12 +220,14 @@ impl Bridge {
source_chain: message.source_chain,
}
.publish(env);

Ok(())
}

/// Cancel a bridge transaction and refund locked tokens
/// Only callable after a timeout period
/// - nonce: The nonce of the bridge transaction to cancel
pub fn cancel_bridge(env: &Env, nonce: u64) {
pub fn cancel_bridge(env: &Env, nonce: u64) -> Result<(), BridgeError> {
// Get bridge transaction
let bridge_txs: Map<u64, BridgeTransaction> = env
.storage()
Expand All @@ -231,13 +236,13 @@ impl Bridge {
.unwrap_or_else(|| Map::new(env));
let bridge_tx = bridge_txs
.get(nonce)
.unwrap_or_else(|| panic!("Bridge transaction not found"));
.ok_or(BridgeError::BridgeTransactionNotFound)?;

// Check timeout (7 days = 604800 seconds)
const TIMEOUT: u64 = 604800;
let elapsed = env.ledger().timestamp() - bridge_tx.timestamp;
if elapsed < TIMEOUT {
panic!("Timeout not reached");
return Err(BridgeError::TimeoutNotReached);
}

// Get token address
Expand All @@ -259,6 +264,8 @@ impl Bridge {
let mut updated_txs = bridge_txs;
updated_txs.remove(nonce);
env.storage().instance().set(&BRIDGE_TXS, &updated_txs);

Ok(())
}

// ========== Admin Functions ==========
Expand Down Expand Up @@ -304,15 +311,17 @@ impl Bridge {
}

/// Set bridge fee (admin only)
pub fn set_bridge_fee(env: &Env, fee: i128) {
pub fn set_bridge_fee(env: &Env, fee: i128) -> Result<(), BridgeError> {
let admin: Address = env.storage().instance().get(&ADMIN).unwrap();
admin.require_auth();

if fee < 0 {
panic!("Fee cannot be negative");
return Err(BridgeError::FeeCannotBeNegative);
}

env.storage().instance().set(&BRIDGE_FEE, &fee);

Ok(())
}

/// Set fee recipient (admin only)
Expand All @@ -324,17 +333,19 @@ impl Bridge {
}

/// Set minimum validators (admin only)
pub fn set_min_validators(env: &Env, min_validators: u32) {
pub fn set_min_validators(env: &Env, min_validators: u32) -> Result<(), BridgeError> {
let admin: Address = env.storage().instance().get(&ADMIN).unwrap();
admin.require_auth();

if min_validators == 0 {
panic!("Minimum validators must be at least 1");
return Err(BridgeError::MinimumValidatorsMustBeAtLeastOne);
}

env.storage()
.instance()
.set(&MIN_VALIDATORS, &min_validators);

Ok(())
}

// ========== View Functions ==========
Expand Down
80 changes: 80 additions & 0 deletions contracts/teachlink/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use soroban_sdk::contracterror;

/// Bridge module errors
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum BridgeError {
AlreadyInitialized = 100,
AmountMustBePositive = 101,
DestinationChainNotSupported = 102,
InsufficientValidatorSignatures = 103,
InvalidValidatorSignature = 104,
NonceAlreadyProcessed = 105,
TokenMismatch = 106,
BridgeTransactionNotFound = 107,
TimeoutNotReached = 108,
FeeCannotBeNegative = 109,
MinimumValidatorsMustBeAtLeastOne = 110,
}

/// Escrow module errors
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum EscrowError {
AmountMustBePositive = 200,
AtLeastOneSignerRequired = 201,
InvalidSignerThreshold = 202,
RefundTimeMustBeInFuture = 203,
RefundTimeMustBeAfterReleaseTime = 204,
DuplicateSigner = 205,
SignerNotAuthorized = 206,
SignerAlreadyApproved = 207,
CallerNotAuthorized = 208,
InsufficientApprovals = 209,
ReleaseTimeNotReached = 210,
OnlyDepositorCanRefund = 211,
RefundNotEnabled = 212,
RefundTimeNotReached = 213,
OnlyDepositorCanCancel = 214,
CannotCancelAfterApprovals = 215,
OnlyDepositorOrBeneficiaryCanDispute = 216,
EscrowNotInDispute = 217,
OnlyArbitratorCanResolve = 218,
EscrowNotPending = 219,
EscrowNotFound = 220,
}

/// Rewards module errors
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum RewardsError {
AlreadyInitialized = 300,
AmountMustBePositive = 301,
InsufficientRewardPoolBalance = 302,
NoRewardsAvailable = 303,
NoPendingRewards = 304,
RateCannotBeNegative = 305,
}

/// Common errors that can be used across modules
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CommonError {
Unauthorized = 400,
InvalidInput = 401,
InsufficientBalance = 402,
TransferFailed = 403,
StorageError = 404,
}

/// Result type alias for bridge operations
pub type BridgeResult<T> = core::result::Result<T, BridgeError>;

/// Result type alias for escrow operations
pub type EscrowResult<T> = core::result::Result<T, EscrowError>;

/// Result type alias for rewards operations
pub type RewardsResult<T> = core::result::Result<T, RewardsError>;

/// Result type alias for common operations
pub type CommonResult<T> = core::result::Result<T, CommonError>;
Loading
Loading