diff --git a/contracts/flash_loan_guard/src/lib.rs b/contracts/flash_loan_guard/src/lib.rs new file mode 100644 index 00000000..0a2d130c --- /dev/null +++ b/contracts/flash_loan_guard/src/lib.rs @@ -0,0 +1,36 @@ +use soroban_sdk::{contract, contractimpl, contracttype, Address, Env}; + +#[derive(Clone)] +#[contracttype] +pub enum DataKey { + DepositBlock(Address), +} + +@contract +pub struct FlashLoanGuardContract; + +@contractimpl +impl FlashLoanGuardContract { + pub fn deposit(env: Env, user: Address) { + user.require_auth(); + let current_block = env.ledger().sequence(); + env.storage().persistent().set(&DataKey::DepositBlock(user.clone()), ¤t_block); + } + + pub fn withdraw(env: Env, user: Address) { + user.require_auth(); + let current_block = env.ledger().sequence(); + + let deposit_block: u32 = env.storage() + .persistent() + .get(&DataKey::DepositBlock(user.clone())) + .unwrap_or(0); + + if current_block <= deposit_block { + panic!("FlashLoanReentrancy: withdrawal prohibited in the same ledger block as deposit"); + } + + // Proceed with withdrawal logic... + env.storage().persistent().remove(&DataKey::DepositBlock(user)); + } +} \ No newline at end of file diff --git a/contracts/flash_loan_guard/src/test.rs b/contracts/flash_loan_guard/src/test.rs new file mode 100644 index 00000000..d8a7ee49 --- /dev/null +++ b/contracts/flash_loan_guard/src/test.rs @@ -0,0 +1,37 @@ +#[cfg(test)] +mod test { + use super::*; + use soroban_sdk::{Env, Address}; + + #[test] + #[should_panic(expected = "FlashLoanReentrancy")] + fn test_prevents_same_block_withdrawal() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(FlashLoanGuardContract, ()); + let client = FlashLoanGuardContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + env.ledger().set_sequence_number(100); + client.deposit(&user); + + // Attempt withdrawal in the same block (100) -> should panic + client.withdraw(&user); + } + + #[test] + fn test_allows_subsequent_block_withdrawal() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(FlashLoanGuardContract, ()); + let client = FlashLoanGuardContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + env.ledger().set_sequence_number(100); + client.deposit(&user); + + // Advance ledger block + env.ledger().set_sequence_number(101); + client.withdraw(&user); // Should succeed without panicking + } +} \ No newline at end of file diff --git a/contracts/yield_vault/src/lib.rs b/contracts/yield_vault/src/lib.rs new file mode 100644 index 00000000..92461fbf --- /dev/null +++ b/contracts/yield_vault/src/lib.rs @@ -0,0 +1,45 @@ +use soroban_sdk::{contract, contractimpl, Address, Env}; + +@contract +pub struct YieldVaultContract; + +@contractimpl +impl YieldVaultContract { + pub fn deposit(env: Env, user: Address, token_amount: i128, min_shares_out: i128) -> i128 { + user.require_auth(); + + // Calculate shares to mint based on current vault exchange rate + let shares_out = Self::calculate_shares_out(&env, token_amount); + + if shares_out < min_shares_out { + panic!("SlippageExceeded: minted shares are less than min_shares_out"); + } + + // Perform deposit accounting and token transfer... + shares_out + } + + pub fn withdraw(env: Env, user: Address, shares_in: i128, min_tokens_out: i128) -> i128 { + user.require_auth(); + + // Calculate tokens to return based on current vault exchange rate + let tokens_out = Self::calculate_tokens_out(&env, shares_in); + + if tokens_out < min_tokens_out { + panic!("SlippageExceeded: returned tokens are less than min_tokens_out"); + } + + // Perform withdrawal accounting and token transfer... + tokens_out + } + + fn calculate_shares_out(_env: &Env, token_amount: i128) -> i128 { + // Mock exchange rate logic: 1:1 ratio for demonstration + token_amount + } + + fn calculate_tokens_out(_env: &Env, shares_in: i128) -> i128 { + // Mock exchange rate logic: 1:1 ratio for demonstration + shares_in + } +} \ No newline at end of file diff --git a/contracts/yield_vault/src/test.rs b/contracts/yield_vault/src/test.rs new file mode 100644 index 00000000..d39b8a30 --- /dev/null +++ b/contracts/yield_vault/src/test.rs @@ -0,0 +1,30 @@ +#[cfg(test)] +mod test { + use super::*; + use soroban_sdk::{Env, Address}; + + #[test] + fn test_deposit_slippage_success() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(YieldVaultContract, ()); + let client = YieldVaultContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + let shares = client.deposit(&user, &1000, &950); + assert_eq!(shares, 1000); + } + + #[test] + #[should_panic(expected = "SlippageExceeded")] + fn test_deposit_slippage_revert() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(YieldVaultContract, ()); + let client = YieldVaultContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + // Expecting 1050 shares out when deposit yields 1000 -> should panic + client.deposit(&user, &1000, &1050); + } +} \ No newline at end of file