From 455defa0bd3ccfecdbe18fef1cb3c5c10b16b28c Mon Sep 17 00:00:00 2001 From: mootz12 Date: Fri, 1 Dec 2023 15:10:31 -0500 Subject: [PATCH 1/5] emitter: feat: add swap timelock and track distribution per backstop --- Cargo.lock | 3 +- Makefile | 2 +- emitter/Cargo.toml | 1 - emitter/src/backstop_manager.rs | 603 +++++++++++++++++++++++++++ emitter/src/contract.rs | 107 +++-- emitter/src/dependencies/backstop.rs | 3 - emitter/src/dependencies/mod.rs | 4 - emitter/src/emitter.rs | 211 ++-------- emitter/src/errors.rs | 4 + emitter/src/lib.rs | 3 +- emitter/src/storage.rs | 141 +++++-- emitter/src/testutils.rs | 10 - 12 files changed, 818 insertions(+), 274 deletions(-) create mode 100644 emitter/src/backstop_manager.rs delete mode 100644 emitter/src/dependencies/backstop.rs delete mode 100644 emitter/src/dependencies/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 37485d83..3815a667 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,7 @@ name = "backstop" version = "0.0.1" dependencies = [ "cast", + "emitter", "fixed-point-math", "mock-pool-factory", "sep-41-token", @@ -401,7 +402,6 @@ dependencies = [ name = "emitter" version = "0.0.1" dependencies = [ - "backstop", "sep-41-token", "soroban-sdk", ] @@ -735,6 +735,7 @@ version = "0.0.1" dependencies = [ "backstop", "cast", + "emitter", "fixed-point-math", "mock-pool-factory", "sep-40-oracle", diff --git a/Makefile b/Makefile index 6a166833..83dc0152 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,9 @@ test: build cargo test --all --tests build: + cargo rustc --manifest-path=emitter/Cargo.toml --crate-type=cdylib --target=wasm32-unknown-unknown --release cargo rustc --manifest-path=pool-factory/Cargo.toml --crate-type=cdylib --target=wasm32-unknown-unknown --release cargo rustc --manifest-path=backstop/Cargo.toml --crate-type=cdylib --target=wasm32-unknown-unknown --release - cargo rustc --manifest-path=emitter/Cargo.toml --crate-type=cdylib --target=wasm32-unknown-unknown --release cargo rustc --manifest-path=pool/Cargo.toml --crate-type=cdylib --target=wasm32-unknown-unknown --release mkdir -p target/wasm32-unknown-unknown/optimized soroban contract optimize \ diff --git a/emitter/Cargo.toml b/emitter/Cargo.toml index 8f21f2b5..94496f55 100644 --- a/emitter/Cargo.toml +++ b/emitter/Cargo.toml @@ -19,5 +19,4 @@ sep-41-token = { workspace = true } [dev_dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } -backstop = { path = "../backstop", features = ["testutils"] } sep-41-token = { workspace = true, features = ["testutils"] } diff --git a/emitter/src/backstop_manager.rs b/emitter/src/backstop_manager.rs new file mode 100644 index 00000000..180b0853 --- /dev/null +++ b/emitter/src/backstop_manager.rs @@ -0,0 +1,603 @@ +use sep_41_token::TokenClient; +use soroban_sdk::{contracttype, panic_with_error, Address, Env}; + +use crate::{emitter, storage, EmitterError}; + +#[derive(Clone)] +#[contracttype] +pub struct Swap { + pub new_backstop: Address, + pub new_backstop_token: Address, + pub unlock_time: u64, +} + +/// Require that the new backstop is larger than the backstop +/// +/// Panics otherwise +fn is_new_backstop_is_larger(e: &Env, new_backstop: &Address, backstop: &Address) -> bool { + let backstop_token = storage::get_backstop_token(e); + let backstop_token_client = TokenClient::new(e, &backstop_token); + + let backstop_balance = backstop_token_client.balance(&backstop); + let new_backstop_balance = backstop_token_client.balance(&new_backstop); + return new_backstop_balance > backstop_balance; +} + +/// Perform a backstop swap +pub fn execute_queue_swap_backstop( + e: &Env, + new_backstop: &Address, + new_backstop_token: &Address, +) -> Swap { + // verify no swap is already queued + if storage::get_queued_swap(e).is_some() { + panic_with_error!(e, EmitterError::SwapAlreadyExists); + } + + let backstop = storage::get_backstop(e); + if !is_new_backstop_is_larger(e, new_backstop, &backstop) { + panic_with_error!(e, EmitterError::InsufficientBackstopSize); + } + + let swap = Swap { + new_backstop: new_backstop.clone(), + new_backstop_token: new_backstop_token.clone(), + unlock_time: e.ledger().timestamp() + 31 * 24 * 60 * 60, + }; + storage::set_queued_swap(e, &swap); + swap +} + +/// Cancel a backstop swap if it has not maintained a higher balance than the current backstop +pub fn execute_cancel_swap_backstop(e: &Env) -> Swap { + let swap = storage::get_queued_swap(e) + .unwrap_or_else(|| panic_with_error!(e, EmitterError::SwapNotQueued)); + + let backstop = storage::get_backstop(e); + if is_new_backstop_is_larger(e, &swap.new_backstop, &backstop) { + panic_with_error!(e, EmitterError::SwapCannotBeCanceled); + } + + storage::del_queued_swap(e); + swap +} + +/// Perform a swap from the queue if it has been unlocked and the new backstop has maintained a higher balance than the current backstop +pub fn execute_swap_backstop(e: &Env) -> Swap { + let swap = storage::get_queued_swap(e) + .unwrap_or_else(|| panic_with_error!(e, EmitterError::SwapNotQueued)); + + if swap.unlock_time > e.ledger().timestamp() { + panic_with_error!(e, EmitterError::SwapNotUnlocked); + } + + let backstop = storage::get_backstop(e); + if !is_new_backstop_is_larger(e, &swap.new_backstop, &backstop) { + panic_with_error!(e, EmitterError::InsufficientBackstopSize); + } + + // distribute before swapping to ensure the old backstop gets their tokens + emitter::execute_distribute(e, &backstop); + + // swap backstop and token + storage::set_last_fork(e, e.ledger().sequence()); + storage::del_queued_swap(e); + storage::set_backstop(e, &swap.new_backstop); + storage::set_backstop_token(e, &swap.new_backstop_token); + + // start distribution for new backstop + storage::set_last_distro_time(e, &swap.new_backstop, e.ledger().timestamp()); + + swap +} + +#[cfg(test)] +mod tests { + + use crate::{constants::SCALAR_7, storage, testutils::create_emitter}; + + use super::*; + use sep_41_token::testutils::MockTokenClient; + use soroban_sdk::testutils::{Address as _, Ledger, LedgerInfo}; + + /********** execute_queue_swap_backstop **********/ + + #[test] + fn test_execute_queue_swap_backstop() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 50, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + let new_backstop_token = Address::random(&e); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_001 * SCALAR_7)); + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 1000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + + execute_queue_swap_backstop(&e, &new_backstop, &new_backstop_token); + + // verify no swap occurred + assert_eq!(storage::get_backstop(&e), backstop); + assert_eq!(storage::get_backstop_token(&e), backstop_token); + assert_eq!(storage::get_last_fork(&e), 123); + + // verify swap is queued + let swap = storage::get_queued_swap(&e); + assert!(swap.is_some()); + let swap = swap.unwrap(); + assert_eq!(swap.new_backstop, new_backstop); + assert_eq!(swap.new_backstop_token, new_backstop_token); + assert_eq!(swap.unlock_time, 2678400 + 12345); // 31 days + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #30)")] + fn test_execute_queue_swap_backstop_insufficient_funds() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 50, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + let new_backstop_token = Address::random(&e); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_000 * SCALAR_7)); + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 1000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + + execute_queue_swap_backstop(&e, &new_backstop, &new_backstop_token); + assert!(false); // should panic + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #60)")] + fn test_execute_queue_swap_backstop_already_exists() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 50, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + let new_backstop_token = Address::random(&e); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_001 * SCALAR_7)); + + let swap = Swap { + new_backstop: Address::random(&e), + new_backstop_token: Address::random(&e), + unlock_time: 0, + }; + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 1000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + storage::set_queued_swap(&e, &swap); + + execute_queue_swap_backstop(&e, &new_backstop, &new_backstop_token); + assert!(false); // should panic + }); + } + + /********** execute_cancel_swap_backstop **********/ + + #[test] + fn test_execute_cancel_swap_backstop() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 50, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + let new_backstop_token = Address::random(&e); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_000 * SCALAR_7)); + + let swap = Swap { + new_backstop: new_backstop.clone(), + new_backstop_token: new_backstop_token.clone(), + unlock_time: 12345 + 1000, + }; + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 1000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + storage::set_queued_swap(&e, &swap); + + execute_cancel_swap_backstop(&e); + + // verify no swap occurred + assert_eq!(storage::get_backstop(&e), backstop); + assert_eq!(storage::get_backstop_token(&e), backstop_token); + assert_eq!(storage::get_last_fork(&e), 123); + + // verify swap is removed + let swap = storage::get_queued_swap(&e); + assert!(swap.is_none()); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #80)")] + fn test_execute_cancel_swap_backstop_valid_swap() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 50, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + let new_backstop_token = Address::random(&e); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_001 * SCALAR_7)); + + let swap = Swap { + new_backstop: new_backstop.clone(), + new_backstop_token: new_backstop_token.clone(), + unlock_time: 12345 + 1000, + }; + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 1000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + storage::set_queued_swap(&e, &swap); + + execute_cancel_swap_backstop(&e); + assert!(false); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #50)")] + fn test_execute_cancel_swap_backstop_none_queued() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 50, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_000 * SCALAR_7)); + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 1000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + + execute_cancel_swap_backstop(&e); + assert!(false); + }); + } + + /********** execute_swap_backstop **********/ + + #[test] + fn test_execute_swap_backstop() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 500, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + + let blnd_token = e.register_stellar_asset_contract(emitter.clone()); + let blnd_token_client = MockTokenClient::new(&e, &blnd_token); + + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + let new_backstop_token = Address::random(&e); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_001 * SCALAR_7)); + + let swap = Swap { + new_backstop: new_backstop.clone(), + new_backstop_token: new_backstop_token.clone(), + unlock_time: 12345, + }; + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 10000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_blnd_token(&e, &blnd_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + storage::set_queued_swap(&e, &swap); + + execute_swap_backstop(&e); + + // verify swap occurred + assert_eq!(storage::get_backstop(&e), new_backstop); + assert_eq!(storage::get_backstop_token(&e), new_backstop_token); + assert_eq!(storage::get_last_fork(&e), 500); + + // verify swap is removed + let swap = storage::get_queued_swap(&e); + assert!(swap.is_none()); + + // verify old backstop was distributed and new backstop distribution begins + assert_eq!(storage::get_last_distro_time(&e, &backstop), 12345); + assert_eq!(storage::get_last_distro_time(&e, &new_backstop), 12345); + assert_eq!(blnd_token_client.balance(&backstop), 2345 * SCALAR_7); + assert_eq!(blnd_token_client.balance(&new_backstop), 0); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #70)")] + fn test_execute_swap_backstop_not_unlocked() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 500, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + + let blnd_token = e.register_stellar_asset_contract(emitter.clone()); + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + let new_backstop_token = Address::random(&e); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_001 * SCALAR_7)); + + let swap = Swap { + new_backstop: new_backstop.clone(), + new_backstop_token: new_backstop_token.clone(), + unlock_time: 12345 + 1, + }; + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 10000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_blnd_token(&e, &blnd_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + storage::set_queued_swap(&e, &swap); + + execute_swap_backstop(&e); + assert!(false); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #50)")] + fn test_execute_swap_backstop_no_queue() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 500, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + + let blnd_token = e.register_stellar_asset_contract(emitter.clone()); + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_001 * SCALAR_7)); + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 10000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_blnd_token(&e, &blnd_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + + execute_swap_backstop(&e); + assert!(false); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #30)")] + fn test_execute_swap_backstop_insufficient_funds() { + let e = Env::default(); + e.mock_all_auths(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 500, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let emitter = create_emitter(&e); + + let blnd_token = e.register_stellar_asset_contract(emitter.clone()); + let backstop = Address::random(&e); + let new_backstop = Address::random(&e); + + let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); + let backstop_token_client = MockTokenClient::new(&e, &backstop_token); + let new_backstop_token = Address::random(&e); + + backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); + backstop_token_client.mint(&new_backstop, &(1_000_000 * SCALAR_7)); + + let swap = Swap { + new_backstop: new_backstop.clone(), + new_backstop_token: new_backstop_token.clone(), + unlock_time: 12345, + }; + + e.as_contract(&emitter, || { + storage::set_last_distro_time(&e, &backstop, 10000); + storage::set_backstop(&e, &backstop); + storage::set_backstop_token(&e, &backstop_token); + storage::set_blnd_token(&e, &blnd_token); + storage::set_drop_status(&e, &backstop); + storage::set_last_fork(&e, 123); + storage::set_queued_swap(&e, &swap); + + execute_swap_backstop(&e); + assert!(false); + }); + } +} diff --git a/emitter/src/contract.rs b/emitter/src/contract.rs index a49dad88..b5957be4 100644 --- a/emitter/src/contract.rs +++ b/emitter/src/contract.rs @@ -1,5 +1,7 @@ -use crate::{emitter, errors::EmitterError, storage}; -use soroban_sdk::{contract, contractclient, contractimpl, panic_with_error, Address, Env, Symbol}; +use crate::{backstop_manager, emitter, errors::EmitterError, storage}; +use soroban_sdk::{ + contract, contractclient, contractimpl, panic_with_error, Address, Env, Map, Symbol, +}; /// ### Emitter /// @@ -12,53 +14,76 @@ pub trait Emitter { /// Initialize the Emitter /// /// ### Arguments - /// * `backstop_id` - The backstop module Address ID - /// * `blnd_token_id` - The Blend token Address ID - fn initialize(e: Env, backstop: Address, blnd_token_id: Address); + /// * `blnd_token` - The Blend token Address the Emitter will distribute + /// * `backstop` - The backstop module address to emit to + /// * `backstop_token` - The token the backstop takes deposits in + fn initialize(e: Env, blnd_token: Address, backstop: Address, backstop_token: Address); /// Distributes BLND tokens to the listed backstop module /// /// Returns the amount of BLND tokens distributed - /// - /// ### Errors - /// If the caller is not the listed backstop module fn distribute(e: Env) -> i128; + /// Fetch the last time the Emitter distributed to the backstop module + /// + /// ### Arguments + /// * `backstop` - The backstop module Address ID + fn get_last_distro(e: Env, backstop_id: Address) -> u64; + /// Fetch the current backstop fn get_backstop(e: Env) -> Address; - /// Switches the listed backstop module to one with more effective backstop deposits - /// - /// Returns OK or an error + /// Queues up a swap of the listed backstop module and token to new addresses. /// /// ### Arguments - /// * `new_backstop_id` - The Address ID of the new backstop module + /// * `new_backstop` - The Address of the new backstop module + /// * `new_backstop_token` - The address of the new backstop token + /// + /// ### Errors + /// If the input contract does not have more backstop deposits than the listed backstop module of the + /// current backstop token. + fn queue_swap_backstop(e: Env, new_backstop: Address, new_backstop_token: Address); + + /// Fetch the queued backstop swap, or None if nothing is queued. + fn get_queued_swap(e: Env) -> Option; + + /// Verifies that a queued swap still meets the requirements to be executed. If not, + /// the queued swap is cancelled and must be recreated. /// /// ### Errors - /// If the input contract does not have more backstop deposits than the listed backstop module - fn swap_backstop(e: Env, new_backstop_id: Address); + /// If the queued swap is still valid. + fn cancel_swap_backstop(e: Env); - /// Distributes initial BLND post-backstop swap or protocol launch + /// Executes a queued swap of the listed backstop module to one with more effective backstop deposits /// - /// Returns OK or an error + /// ### Errors + /// If the input contract does not have more backstop deposits than the listed backstop module, + /// or if the queued swap has not been unlocked. + fn swap_backstop(e: Env); + + /// Distributes initial BLND after a new backstop is set + /// + /// ### Arguments + /// * `list` - The list of address and amounts to distribute too /// /// ### Errors - /// If drop has already been called for this backstop - fn drop(e: Env); + /// If drop has already been called for the backstop, the backstop is not the caller, + /// or the list exceeds the drop amount maximum. + fn drop(e: Env, list: Map); } #[contractimpl] impl Emitter for EmitterContract { - fn initialize(e: Env, backstop: Address, blnd_token_id: Address) { + fn initialize(e: Env, blnd_token: Address, backstop: Address, backstop_token: Address) { storage::bump_instance(&e); - if storage::has_backstop(&e) { + if storage::has_blnd_token(&e) { panic_with_error!(&e, EmitterError::AlreadyInitialized) } + storage::set_blnd_token(&e, &blnd_token); storage::set_backstop(&e, &backstop); - storage::set_blend_id(&e, &blnd_token_id); - storage::set_last_fork(&e, 0); // We set the block 45 days in the past to allow for an immediate initial drop - storage::set_last_distro_time(&e, &(e.ledger().timestamp() - 7 * 24 * 60 * 60)); + storage::set_backstop_token(&e, &backstop_token); + storage::set_last_distro_time(&e, &backstop, e.ledger().timestamp()); } fn distribute(e: Env) -> i128 { @@ -74,22 +99,46 @@ impl Emitter for EmitterContract { distribution_amount } + fn get_last_distro(e: Env, backstop_id: Address) -> u64 { + storage::get_last_distro_time(&e, &backstop_id) + } + fn get_backstop(e: Env) -> Address { storage::get_backstop(&e) } - fn swap_backstop(e: Env, new_backstop_id: Address) { + fn queue_swap_backstop(e: Env, new_backstop: Address, new_backstop_token: Address) { storage::bump_instance(&e); - emitter::execute_swap_backstop(&e, new_backstop_id.clone()); + let swap = + backstop_manager::execute_queue_swap_backstop(&e, &new_backstop, &new_backstop_token); e.events() - .publish((Symbol::new(&e, "swap"),), (new_backstop_id,)); + .publish((Symbol::new(&e, "queue_swap_backstop"),), swap); + } + + fn get_queued_swap(e: Env) -> Option { + storage::get_queued_swap(&e) + } + + fn cancel_swap_backstop(e: Env) { + storage::bump_instance(&e); + let swap = backstop_manager::execute_cancel_swap_backstop(&e); + + e.events() + .publish((Symbol::new(&e, "cancel_swap_backstop"),), swap); + } + + fn swap_backstop(e: Env) { + storage::bump_instance(&e); + let swap = backstop_manager::execute_swap_backstop(&e); + + e.events().publish((Symbol::new(&e, "swap"),), swap); } - fn drop(e: Env) { + fn drop(e: Env, list: Map) { storage::bump_instance(&e); - let drop_list = emitter::execute_drop(&e); + emitter::execute_drop(&e, &list); - e.events().publish((Symbol::new(&e, "drop"),), drop_list); + e.events().publish((Symbol::new(&e, "drop"),), list); } } diff --git a/emitter/src/dependencies/backstop.rs b/emitter/src/dependencies/backstop.rs deleted file mode 100644 index 00624246..00000000 --- a/emitter/src/dependencies/backstop.rs +++ /dev/null @@ -1,3 +0,0 @@ -use soroban_sdk::contractimport; - -contractimport!(file = "../target/wasm32-unknown-unknown/release/backstop.wasm"); diff --git a/emitter/src/dependencies/mod.rs b/emitter/src/dependencies/mod.rs deleted file mode 100644 index d6d77753..00000000 --- a/emitter/src/dependencies/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod backstop; -pub use backstop::Client as BackstopClient; -#[cfg(any(test, feature = "testutils"))] -pub use backstop::{BackstopDataKey, WASM as BACKSTOP_WASM}; diff --git a/emitter/src/emitter.rs b/emitter/src/emitter.rs index 64e90415..c9fddf85 100644 --- a/emitter/src/emitter.rs +++ b/emitter/src/emitter.rs @@ -1,41 +1,27 @@ -use crate::{constants::SCALAR_7, dependencies::BackstopClient, errors::EmitterError, storage}; -use sep_41_token::{StellarAssetClient, TokenClient}; +use crate::{constants::SCALAR_7, errors::EmitterError, storage}; +use sep_41_token::StellarAssetClient; use soroban_sdk::{panic_with_error, Address, Env, Map}; /// Perform a distribution pub fn execute_distribute(e: &Env, backstop: &Address) -> i128 { let timestamp = e.ledger().timestamp(); - let seconds_since_last_distro = timestamp - storage::get_last_distro_time(e); + let seconds_since_last_distro = timestamp - storage::get_last_distro_time(e, backstop); // Blend tokens are distributed at a rate of 1 token per second let distribution_amount = (seconds_since_last_distro as i128) * SCALAR_7; - storage::set_last_distro_time(e, ×tamp); + storage::set_last_distro_time(e, backstop, timestamp); - let blend_id = storage::get_blend_id(e); - let blend_client = StellarAssetClient::new(e, &blend_id); - blend_client.mint(backstop, &distribution_amount); + let blnd_id = storage::get_blnd_token(e); + let blnd_client = StellarAssetClient::new(e, &blnd_id); + blnd_client.mint(backstop, &distribution_amount); distribution_amount } -/// Perform a backstop swap -pub fn execute_swap_backstop(e: &Env, new_backstop_id: Address) { - let backstop = storage::get_backstop(e); - let backstop_token = BackstopClient::new(e, &backstop).backstop_token(); - let backstop_token_client = TokenClient::new(e, &backstop_token); - - let backstop_balance = backstop_token_client.balance(&backstop); - let new_backstop_balance = backstop_token_client.balance(&new_backstop_id); - if new_backstop_balance > backstop_balance { - storage::set_backstop(e, &new_backstop_id); - storage::set_last_fork(e, e.ledger().sequence()); - } else { - panic_with_error!(e, EmitterError::InsufficientBackstopSize); - } -} - /// Perform drop BLND distribution -pub fn execute_drop(e: &Env) -> Map { +pub fn execute_drop(e: &Env, list: &Map) { let backstop = storage::get_backstop(e); + backstop.require_auth(); + if storage::get_drop_status(e, &backstop) { panic_with_error!(e, EmitterError::BadDrop); } @@ -44,11 +30,8 @@ pub fn execute_drop(e: &Env) -> Map { panic_with_error!(e, EmitterError::BadDrop); } - let backstop = storage::get_backstop(e); - let backstop_client = BackstopClient::new(e, &backstop); - let drop_list: Map = backstop_client.drop_list(); let mut drop_amount = 0; - for (_, amt) in drop_list.iter() { + for (_, amt) in list.iter() { drop_amount += amt; } // drop cannot be more than 50 million tokens @@ -56,22 +39,18 @@ pub fn execute_drop(e: &Env) -> Map { panic_with_error!(e, EmitterError::BadDrop); } - let blnd_id = storage::get_blend_id(e); + let blnd_id = storage::get_blnd_token(e); let blnd_client = StellarAssetClient::new(e, &blnd_id); - for (addr, amt) in drop_list.iter() { + for (addr, amt) in list.iter() { blnd_client.mint(&addr, &amt); } storage::set_drop_status(e, &backstop); - drop_list } #[cfg(test)] mod tests { - use crate::{ - storage, - testutils::{create_backstop, create_emitter}, - }; + use crate::{storage, testutils::create_emitter}; use super::*; use sep_41_token::testutils::MockTokenClient; @@ -103,105 +82,14 @@ mod tests { let blnd_client = MockTokenClient::new(&e, &blnd_id); e.as_contract(&emitter, || { - storage::set_last_distro_time(&e, &1000); + storage::set_last_distro_time(&e, &backstop, 1000); storage::set_backstop(&e, &backstop); - storage::set_blend_id(&e, &blnd_id); + storage::set_blnd_token(&e, &blnd_id); let result = execute_distribute(&e, &backstop); assert_eq!(result, 11345_0000000); assert_eq!(blnd_client.balance(&backstop), 11345_0000000); - assert_eq!(storage::get_last_distro_time(&e), 12345); - }); - } - - #[test] - fn test_swap_backstop() { - let e = Env::default(); - e.mock_all_auths(); - - e.ledger().set(LedgerInfo { - timestamp: 12345, - protocol_version: 20, - sequence_number: 50, - network_id: Default::default(), - base_reserve: 10, - min_temp_entry_expiration: 10, - min_persistent_entry_expiration: 10, - max_entry_expiration: 2000000, - }); - - let bombadil = Address::random(&e); - let emitter = create_emitter(&e); - let (backstop, backstop_client) = create_backstop(&e); - let new_backstop = Address::random(&e); - - let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); - let backstop_token_client = MockTokenClient::new(&e, &backstop_token); - - backstop_client.initialize( - &backstop_token, - &Address::random(&e), - &Address::random(&e), - &Address::random(&e), - &Map::new(&e), - ); - - backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); - backstop_token_client.mint(&new_backstop, &(1_000_001 * SCALAR_7)); - - e.as_contract(&emitter, || { - storage::set_last_distro_time(&e, &1000); - storage::set_backstop(&e, &backstop); - storage::set_drop_status(&e, &backstop); - - execute_swap_backstop(&e, new_backstop.clone()); - assert_eq!(storage::get_backstop(&e), new_backstop); - assert_eq!(storage::get_drop_status(&e, &new_backstop), false); - }); - } - - #[test] - #[should_panic(expected = "Error(Contract, #30)")] - fn test_swap_backstop_not_enough() { - let e = Env::default(); - e.mock_all_auths(); - - e.ledger().set(LedgerInfo { - timestamp: 12345, - protocol_version: 20, - sequence_number: 50, - network_id: Default::default(), - base_reserve: 10, - min_temp_entry_expiration: 10, - min_persistent_entry_expiration: 10, - max_entry_expiration: 2000000, - }); - - let bombadil = Address::random(&e); - let emitter = create_emitter(&e); - let (backstop, backstop_client) = create_backstop(&e); - let new_backstop = Address::random(&e); - - let backstop_token = e.register_stellar_asset_contract(bombadil.clone()); - let backstop_token_client = MockTokenClient::new(&e, &backstop_token); - - backstop_client.initialize( - &backstop_token, - &Address::random(&e), - &Address::random(&e), - &Address::random(&e), - &Map::new(&e), - ); - - backstop_token_client.mint(&backstop, &(1_000_000 * SCALAR_7)); - backstop_token_client.mint(&new_backstop, &(1_000_000 * SCALAR_7)); - - e.as_contract(&emitter, || { - storage::set_last_distro_time(&e, &1000); - storage::set_backstop(&e, &backstop); - - execute_swap_backstop(&e, new_backstop.clone()); - assert!(false, "Should have panicked"); + assert_eq!(storage::get_last_distro_time(&e, &backstop), 12345); }); } @@ -224,7 +112,7 @@ mod tests { let frodo = Address::random(&e); let samwise = Address::random(&e); let emitter = create_emitter(&e); - let (backstop, backstop_client) = create_backstop(&e); + let backstop = Address::random(&e); let blnd_id = e.register_stellar_asset_contract(emitter.clone()); let blnd_client = MockTokenClient::new(&e, &blnd_id); @@ -234,23 +122,14 @@ mod tests { (samwise.clone(), 30_000_000 * SCALAR_7) ]; - backstop_client.initialize( - &Address::random(&e), - &blnd_id, - &Address::random(&e), - &Address::random(&e), - &drop_list, - ); - e.as_contract(&emitter, || { - storage::set_last_distro_time(&e, &1000); + storage::set_last_distro_time(&e, &backstop, 1000); storage::set_backstop(&e, &backstop); - storage::set_blend_id(&e, &blnd_id); + storage::set_blnd_token(&e, &blnd_id); storage::set_last_fork(&e, 4000000); - let list = execute_drop(&e); + execute_drop(&e, &drop_list); assert_eq!(storage::get_drop_status(&e, &backstop), true); - assert_eq!(list.len(), 2); assert_eq!(blnd_client.balance(&frodo), 20_000_000 * SCALAR_7); assert_eq!(blnd_client.balance(&samwise), 30_000_000 * SCALAR_7); }); @@ -276,7 +155,7 @@ mod tests { let frodo = Address::random(&e); let samwise = Address::random(&e); let emitter = create_emitter(&e); - let (backstop, backstop_client) = create_backstop(&e); + let backstop = Address::random(&e); let blnd_id = e.register_stellar_asset_contract(emitter.clone()); let drop_list = map![ @@ -285,22 +164,14 @@ mod tests { (samwise.clone(), 30_000_000 * SCALAR_7) ]; - backstop_client.initialize( - &Address::random(&e), - &blnd_id, - &Address::random(&e), - &Address::random(&e), - &drop_list, - ); - e.as_contract(&emitter, || { - storage::set_last_distro_time(&e, &1000); + storage::set_last_distro_time(&e, &backstop, 1000); storage::set_backstop(&e, &backstop); - storage::set_blend_id(&e, &blnd_id); + storage::set_blnd_token(&e, &blnd_id); storage::set_drop_status(&e, &backstop); storage::set_last_fork(&e, 4000000); - execute_drop(&e); + execute_drop(&e, &drop_list); assert_eq!(storage::get_drop_status(&e, &backstop), true); }); } @@ -325,7 +196,7 @@ mod tests { let frodo = Address::random(&e); let samwise = Address::random(&e); let emitter = create_emitter(&e); - let (backstop, backstop_client) = create_backstop(&e); + let backstop = Address::random(&e); let blnd_id = e.register_stellar_asset_contract(emitter.clone()); let drop_list = map![ @@ -334,21 +205,13 @@ mod tests { (samwise.clone(), 30_000_001 * SCALAR_7) ]; - backstop_client.initialize( - &Address::random(&e), - &blnd_id, - &Address::random(&e), - &Address::random(&e), - &drop_list, - ); - e.as_contract(&emitter, || { - storage::set_last_distro_time(&e, &1000); + storage::set_last_distro_time(&e, &backstop, 1000); storage::set_backstop(&e, &backstop); - storage::set_blend_id(&e, &blnd_id); + storage::set_blnd_token(&e, &blnd_id); storage::set_last_fork(&e, 4000000); - execute_drop(&e); + execute_drop(&e, &drop_list); assert_eq!(storage::get_drop_status(&e, &backstop), false); }); } @@ -374,7 +237,7 @@ mod tests { let frodo = Address::random(&e); let samwise = Address::random(&e); let emitter = create_emitter(&e); - let (backstop, backstop_client) = create_backstop(&e); + let backstop = Address::random(&e); let blnd_id = e.register_stellar_asset_contract(bombadil.clone()); let drop_list = map![ @@ -383,21 +246,13 @@ mod tests { (samwise.clone(), 30_000_000 * SCALAR_7) ]; - backstop_client.initialize( - &Address::random(&e), - &blnd_id, - &Address::random(&e), - &Address::random(&e), - &drop_list, - ); - e.as_contract(&emitter, || { - storage::set_last_distro_time(&e, &1000); + storage::set_last_distro_time(&e, &backstop, 1000); storage::set_backstop(&e, &backstop); - storage::set_blend_id(&e, &blnd_id); + storage::set_blnd_token(&e, &blnd_id); storage::set_last_fork(&e, 5000000); - execute_drop(&e); + execute_drop(&e, &drop_list); }); } } diff --git a/emitter/src/errors.rs b/emitter/src/errors.rs index 41d8b3ef..4e40edbf 100644 --- a/emitter/src/errors.rs +++ b/emitter/src/errors.rs @@ -8,4 +8,8 @@ pub enum EmitterError { NotAuthorized = 20, InsufficientBackstopSize = 30, BadDrop = 40, + SwapNotQueued = 50, + SwapAlreadyExists = 60, + SwapNotUnlocked = 70, + SwapCannotBeCanceled = 80, } diff --git a/emitter/src/lib.rs b/emitter/src/lib.rs index e48f55cb..b93a081d 100644 --- a/emitter/src/lib.rs +++ b/emitter/src/lib.rs @@ -3,6 +3,7 @@ #[cfg(any(test, feature = "testutils"))] extern crate std; +mod backstop_manager; mod constants; mod contract; mod emitter; @@ -10,8 +11,6 @@ mod errors; mod storage; mod testutils; -mod dependencies; - pub use contract::*; pub use errors::EmitterError; pub use storage::EmitterDataKey; diff --git a/emitter/src/storage.rs b/emitter/src/storage.rs index 9b595bf6..e3366849 100644 --- a/emitter/src/storage.rs +++ b/emitter/src/storage.rs @@ -1,28 +1,26 @@ -use soroban_sdk::{contracttype, unwrap::UnwrapOptimized, Address, Env}; +use soroban_sdk::{contracttype, unwrap::UnwrapOptimized, Address, Env, Symbol}; + +use crate::backstop_manager::Swap; pub(crate) const LEDGER_THRESHOLD_SHARED: u32 = 172800; // ~ 10 days pub(crate) const LEDGER_BUMP_SHARED: u32 = 241920; // ~ 14 days /********** Storage **********/ +const BACKSTOP_KEY: &str = "Backstop"; +const BACKSTOP_TOKEN_KEY: &str = "BToken"; +const BLND_TOKEN_KEY: &str = "BLNDTkn"; +const LAST_FORK_KEY: &str = "LastFork"; +const SWAP_KEY: &str = "Swap"; + // Emitter Data Keys #[derive(Clone)] #[contracttype] pub enum EmitterDataKey { - // The address of the backstop module contract - Backstop, - /// TODO: Delete after address <-> bytesN support, - BstopId, - // The address of the blend token contract - BlendId, - // The address of the blend lp token contract - BlendLPId, // The last timestamp distribution was ran on - LastDistro, + LastDistro(Address), // Stores the list of backstop addresses that have dropped Dropped(Address), - // The last block emissions were forked - LastFork, } /// Bump the instance rent for the contract. Bumps for 10 days due to the 7-day cycle window of this contract @@ -34,53 +32,107 @@ pub fn bump_instance(e: &Env) { /********** Backstop **********/ -/// Fetch the current backstop id +/// Fetch the current backstop address /// /// Returns current backstop module contract address pub fn get_backstop(e: &Env) -> Address { e.storage() .instance() - .get(&EmitterDataKey::Backstop) + .get(&Symbol::new(e, BACKSTOP_KEY)) .unwrap_optimized() } -/// Set a new backstop id +/// Set a new backstop address /// /// ### Arguments -/// * `new_backstop_id` - The id for the new backstop -pub fn set_backstop(e: &Env, new_backstop_id: &Address) { +/// * `new_backstop` - The new backstop module contract address +pub fn set_backstop(e: &Env, new_backstop: &Address) { e.storage() .instance() - .set::(&EmitterDataKey::Backstop, new_backstop_id); + .set::(&Symbol::new(e, BACKSTOP_KEY), new_backstop); } -/// Check if a backstop has been set +/// Fetch the current backstop token address /// -/// Returns true if a backstop has been set -pub fn has_backstop(e: &Env) -> bool { - e.storage().instance().has(&EmitterDataKey::Backstop) +/// Returns current backstop module contract address +pub fn get_backstop_token(e: &Env) -> Address { + e.storage() + .instance() + .get(&Symbol::new(e, BACKSTOP_TOKEN_KEY)) + .unwrap_optimized() +} + +/// Set a new backstop token address +/// +/// ### Arguments +/// * `new_backstop_token` - The new backstop token contract address +pub fn set_backstop_token(e: &Env, new_backstop_token: &Address) { + e.storage() + .instance() + .set::(&Symbol::new(e, BACKSTOP_TOKEN_KEY), new_backstop_token); +} + +/// Fetch the current queued backstop swap, or None +pub fn get_queued_swap(e: &Env) -> Option { + if let Some(result) = e.storage().persistent().get(&Symbol::new(e, SWAP_KEY)) { + e.storage().persistent().bump( + &Symbol::new(e, SWAP_KEY), + LEDGER_THRESHOLD_SHARED, + LEDGER_BUMP_SHARED, + ); + Some(result) + } else { + None + } +} + +/// Set a new swap in the queue +/// +/// ### Arguments +/// * `swap` - The swap to queue +pub fn set_queued_swap(e: &Env, swap: &Swap) { + e.storage() + .persistent() + .set::(&Symbol::new(e, SWAP_KEY), swap); + e.storage().persistent().bump( + &Symbol::new(e, SWAP_KEY), + LEDGER_THRESHOLD_SHARED, + LEDGER_BUMP_SHARED, + ); +} + +/// Fetch the current queued backstop swap, or None +pub fn del_queued_swap(e: &Env) { + e.storage().persistent().remove(&Symbol::new(e, SWAP_KEY)); } /********** Blend **********/ -/// Fetch the blend token address +/// Fetch the BLND token address /// /// Returns blend token address -pub fn get_blend_id(e: &Env) -> Address { +pub fn get_blnd_token(e: &Env) -> Address { e.storage() .instance() - .get(&EmitterDataKey::BlendId) + .get(&Symbol::new(e, BLND_TOKEN_KEY)) .unwrap_optimized() } -/// Set the blend token address +/// Set the BLND token address /// /// ### Arguments -/// * `blend_id` - The blend token address -pub fn set_blend_id(e: &Env, blend_id: &Address) { +/// * `BLND` - The blend token address +pub fn set_blnd_token(e: &Env, blnd_token: &Address) { e.storage() .instance() - .set::(&EmitterDataKey::BlendId, blend_id); + .set::(&Symbol::new(e, BLND_TOKEN_KEY), blnd_token); +} + +/// Check if the BLND token has been set +/// +/// Returns true if a BLND token has been set +pub fn has_blnd_token(e: &Env) -> bool { + e.storage().instance().has(&Symbol::new(e, BLND_TOKEN_KEY)) } /********** Blend Distributions **********/ @@ -88,31 +140,30 @@ pub fn set_blend_id(e: &Env, blend_id: &Address) { /// Fetch the last timestamp distribution was ran on /// /// Returns the last timestamp distribution was ran on -pub fn get_last_distro_time(e: &Env) -> u64 { - e.storage().persistent().bump( - &EmitterDataKey::LastDistro, - LEDGER_THRESHOLD_SHARED, - LEDGER_BUMP_SHARED, - ); +/// +/// ### Arguments +/// * `backstop` - The backstop module Address +pub fn get_last_distro_time(e: &Env, backstop: &Address) -> u64 { + // don't need to bump while reading since this value is set on every distribution e.storage() .persistent() - .get(&EmitterDataKey::LastDistro) + .get(&EmitterDataKey::LastDistro(backstop.clone())) .unwrap_optimized() } /// Set the last timestamp distribution was ran on /// /// ### Arguments +/// * `backstop` - The backstop module Address /// * `last_distro` - The last timestamp distribution was ran on -pub fn set_last_distro_time(e: &Env, last_distro: &u64) { +pub fn set_last_distro_time(e: &Env, backstop: &Address, last_distro: u64) { + let key = EmitterDataKey::LastDistro(backstop.clone()); e.storage() .persistent() - .set::(&EmitterDataKey::LastDistro, last_distro); - e.storage().persistent().bump( - &EmitterDataKey::LastDistro, - LEDGER_THRESHOLD_SHARED, - LEDGER_BUMP_SHARED, - ); + .set::(&key, &last_distro); + e.storage() + .persistent() + .bump(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); } /// Get whether the emitter has performed the drop distribution or not for the current backstop @@ -141,7 +192,7 @@ pub fn set_drop_status(e: &Env, backstop: &Address) { pub fn get_last_fork(e: &Env) -> u32 { e.storage() .instance() - .get(&EmitterDataKey::LastFork) + .get(&Symbol::new(e, LAST_FORK_KEY)) .unwrap_optimized() } @@ -152,5 +203,5 @@ pub fn get_last_fork(e: &Env) -> u32 { pub fn set_last_fork(e: &Env, block: u32) { e.storage() .instance() - .set::(&EmitterDataKey::LastFork, &block); + .set::(&Symbol::new(e, LAST_FORK_KEY), &block); } diff --git a/emitter/src/testutils.rs b/emitter/src/testutils.rs index 89d755e1..c9410085 100644 --- a/emitter/src/testutils.rs +++ b/emitter/src/testutils.rs @@ -2,18 +2,8 @@ use soroban_sdk::{Address, Env}; -use backstop::{BackstopClient, BackstopContract}; - use crate::EmitterContract; pub(crate) fn create_emitter(e: &Env) -> Address { e.register_contract(None, EmitterContract {}) } - -pub(crate) fn create_backstop(e: &Env) -> (Address, BackstopClient) { - let contract_address = e.register_contract(None, BackstopContract {}); - ( - contract_address.clone(), - BackstopClient::new(e, &contract_address), - ) -} From 6f5c7db704d162eda72f0024873a7b76065b710a Mon Sep 17 00:00:00 2001 From: mootz12 Date: Fri, 1 Dec 2023 15:11:59 -0500 Subject: [PATCH 2/5] backstop: feat: fix lost emissions by distributing emissions to pools over EPS --- backstop/Cargo.toml | 1 + backstop/src/backstop/deposit.rs | 36 +- backstop/src/backstop/fund_management.rs | 95 ++-- backstop/src/backstop/mod.rs | 3 +- backstop/src/backstop/pool.rs | 166 ++++++- backstop/src/backstop/withdrawal.rs | 22 +- backstop/src/contract.rs | 47 +- backstop/src/dependencies/emitter.rs | 3 + backstop/src/dependencies/mod.rs | 3 + backstop/src/emissions/distributor.rs | 38 +- backstop/src/emissions/manager.rs | 565 +++++++++++++++++------ backstop/src/emissions/mod.rs | 2 +- backstop/src/storage.rs | 130 ++++-- backstop/src/testutils.rs | 43 +- 14 files changed, 865 insertions(+), 289 deletions(-) create mode 100644 backstop/src/dependencies/emitter.rs diff --git a/backstop/Cargo.toml b/backstop/Cargo.toml index 32ec4852..76726cd0 100644 --- a/backstop/Cargo.toml +++ b/backstop/Cargo.toml @@ -24,4 +24,5 @@ sep-41-token = { workspace = true } [dev_dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } mock-pool-factory = { path = "../mocks/mock-pool-factory", features = ["testutils"] } +emitter = { path = "../emitter", features = ["testutils"] } sep-41-token = { workspace = true, features = ["testutils"] } diff --git a/backstop/src/backstop/deposit.rs b/backstop/src/backstop/deposit.rs index ac81c3a4..664a3201 100644 --- a/backstop/src/backstop/deposit.rs +++ b/backstop/src/backstop/deposit.rs @@ -2,10 +2,13 @@ use crate::{contract::require_nonnegative, emissions, storage}; use sep_41_token::TokenClient; use soroban_sdk::{Address, Env}; +use super::require_is_from_pool_factory; + /// Perform a deposit into the backstop module pub fn execute_deposit(e: &Env, from: &Address, pool_address: &Address, amount: i128) -> i128 { require_nonnegative(e, amount); let mut pool_balance = storage::get_pool_balance(e, pool_address); + require_is_from_pool_factory(e, pool_address, pool_balance.shares); let mut user_balance = storage::get_user_balance(e, pool_address, from); emissions::update_emissions(e, pool_address, &pool_balance, from, &user_balance, false); @@ -29,7 +32,7 @@ mod tests { use crate::{ backstop::execute_donate, - testutils::{create_backstop, create_backstop_token}, + testutils::{create_backstop, create_backstop_token, create_mock_pool_factory}, }; use super::*; @@ -51,6 +54,10 @@ mod tests { backstop_token_client.mint(&samwise, &100_0000000); backstop_token_client.mint(&frodo, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_0_id); + mock_pool_factory_client.set_pool(&pool_1_id); + // initialize pool 0 with funds + some profit e.as_contract(&backstop_address, || { execute_deposit(&e, &frodo, &pool_0_id, 25_0000000); @@ -101,6 +108,9 @@ mod tests { let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); backstop_token_client.mint(&samwise, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_0_id); + e.as_contract(&backstop_address, || { execute_deposit(&e, &samwise, &pool_0_id, 100_0000001); @@ -123,8 +133,32 @@ mod tests { let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); backstop_token_client.mint(&samwise, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_0_id); + e.as_contract(&backstop_address, || { execute_deposit(&e, &samwise, &pool_0_id, -100); }); } + + #[test] + #[should_panic(expected = "Error(Contract, #10)")] + fn text_execute_deposit_not_pool() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + + let backstop_address = create_backstop(&e); + let pool_0_id = Address::random(&e); + let bombadil = Address::random(&e); + let samwise = Address::random(&e); + + let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); + backstop_token_client.mint(&samwise, &100_0000000); + + create_mock_pool_factory(&e, &backstop_address); + + e.as_contract(&backstop_address, || { + execute_deposit(&e, &samwise, &pool_0_id, 100); + }); + } } diff --git a/backstop/src/backstop/fund_management.rs b/backstop/src/backstop/fund_management.rs index 4bafa128..777aace9 100644 --- a/backstop/src/backstop/fund_management.rs +++ b/backstop/src/backstop/fund_management.rs @@ -10,7 +10,6 @@ use super::require_is_from_pool_factory; /// Perform a draw from a pool's backstop pub fn execute_draw(e: &Env, pool_address: &Address, amount: i128, to: &Address) { require_nonnegative(e, amount); - require_is_from_pool_factory(e, pool_address); let mut pool_balance = storage::get_pool_balance(e, pool_address); @@ -25,10 +24,12 @@ pub fn execute_draw(e: &Env, pool_address: &Address, amount: i128, to: &Address) pub fn execute_donate(e: &Env, from: &Address, pool_address: &Address, amount: i128) { require_nonnegative(e, amount); + let mut pool_balance = storage::get_pool_balance(e, pool_address); + require_is_from_pool_factory(e, pool_address, pool_balance.shares); + let backstop_token = TokenClient::new(e, &storage::get_backstop_token(e)); backstop_token.transfer(from, &e.current_contract_address(), &amount); - let mut pool_balance = storage::get_pool_balance(e, pool_address); pool_balance.deposit(amount, 0); storage::set_pool_balance(e, pool_address, &pool_balance); } @@ -37,10 +38,12 @@ pub fn execute_donate(e: &Env, from: &Address, pool_address: &Address, amount: i pub fn execute_donate_usdc(e: &Env, from: &Address, pool_address: &Address, amount: i128) { require_nonnegative(e, amount); + let mut pool_usdc = storage::get_pool_usdc(e, pool_address); + require_is_from_pool_factory(e, pool_address, pool_usdc); + let usdc_token = TokenClient::new(e, &storage::get_usdc_token(e)); usdc_token.transfer(from, &e.current_contract_address(), &amount); - let mut pool_usdc = storage::get_pool_usdc(e, pool_address); pool_usdc += amount; storage::set_pool_usdc(e, pool_address, &pool_usdc); } @@ -122,6 +125,9 @@ mod tests { backstop_token_client.mint(&samwise, &100_0000000); backstop_token_client.mint(&frodo, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + mock_pool_factory_client.set_pool(&pool_0_id); + // initialize pool 0 with funds e.as_contract(&backstop_id, || { execute_deposit(&e, &frodo, &pool_0_id, 25_0000000); @@ -153,6 +159,9 @@ mod tests { backstop_token_client.mint(&samwise, &100_0000000); backstop_token_client.mint(&frodo, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + mock_pool_factory_client.set_pool(&pool_0_id); + // initialize pool 0 with funds e.as_contract(&backstop_id, || { execute_deposit(&e, &frodo, &pool_0_id, 25_0000000); @@ -164,66 +173,60 @@ mod tests { } #[test] - fn test_execute_draw() { + #[should_panic(expected = "Error(Contract, #10)")] + fn test_execute_donate_not_pool() { let e = Env::default(); e.mock_all_auths_allowing_non_root_auth(); e.budget().reset_unlimited(); - let backstop_address = create_backstop(&e); + let backstop_id = create_backstop(&e); let pool_0_id = Address::random(&e); let bombadil = Address::random(&e); let samwise = Address::random(&e); let frodo = Address::random(&e); - let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); + let (_, backstop_token_client) = create_backstop_token(&e, &backstop_id, &bombadil); + backstop_token_client.mint(&samwise, &100_0000000); backstop_token_client.mint(&frodo, &100_0000000); - let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); - mock_pool_factory_client.set_pool(&pool_0_id); - - // initialize pool 0 with funds - e.as_contract(&backstop_address, || { - execute_deposit(&e, &frodo, &pool_0_id, 50_0000000); - }); + create_mock_pool_factory(&e, &backstop_id); - e.as_contract(&backstop_address, || { - execute_draw(&e, &pool_0_id, 30_0000000, &samwise); - - let new_pool_balance = storage::get_pool_balance(&e, &pool_0_id); - assert_eq!(new_pool_balance.shares, 50_0000000); - assert_eq!(new_pool_balance.tokens, 20_0000000); - assert_eq!(backstop_token_client.balance(&backstop_address), 20_0000000); - assert_eq!(backstop_token_client.balance(&samwise), 30_0000000); + e.as_contract(&backstop_id, || { + execute_donate(&e, &samwise, &pool_0_id, 30_0000000); }); } #[test] - #[should_panic(expected = "Error(Contract, #10)")] - fn test_execute_draw_requires_pool_factory_verification() { + fn test_execute_draw() { let e = Env::default(); e.mock_all_auths_allowing_non_root_auth(); e.budget().reset_unlimited(); - let backstop_id = create_backstop(&e); + let backstop_address = create_backstop(&e); let pool_0_id = Address::random(&e); - let pool_bad_id = Address::random(&e); let bombadil = Address::random(&e); let samwise = Address::random(&e); let frodo = Address::random(&e); - let (_, backstop_token_client) = create_backstop_token(&e, &backstop_id, &bombadil); + let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); backstop_token_client.mint(&frodo, &100_0000000); - let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); mock_pool_factory_client.set_pool(&pool_0_id); // initialize pool 0 with funds - e.as_contract(&backstop_id, || { + e.as_contract(&backstop_address, || { execute_deposit(&e, &frodo, &pool_0_id, 50_0000000); }); - e.as_contract(&backstop_id, || { - execute_draw(&e, &pool_bad_id, 30_0000000, &samwise); + e.as_contract(&backstop_address, || { + execute_draw(&e, &pool_0_id, 30_0000000, &samwise); + + let new_pool_balance = storage::get_pool_balance(&e, &pool_0_id); + assert_eq!(new_pool_balance.shares, 50_0000000); + assert_eq!(new_pool_balance.tokens, 20_0000000); + assert_eq!(backstop_token_client.balance(&backstop_address), 20_0000000); + assert_eq!(backstop_token_client.balance(&samwise), 30_0000000); }); } @@ -246,6 +249,7 @@ mod tests { let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); mock_pool_factory_client.set_pool(&pool_0_id); + mock_pool_factory_client.set_pool(&pool_1_id); // initialize pool 0 with funds e.as_contract(&backstop_id, || { @@ -303,6 +307,9 @@ mod tests { usdc_token_client.mint(&samwise, &100_0000000); usdc_token_client.mint(&frodo, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + mock_pool_factory_client.set_pool(&pool_0_id); + e.as_contract(&backstop_id, || { execute_donate_usdc(&e, &samwise, &pool_0_id, 30_0000000); let new_pool_usdc = storage::get_pool_usdc(&e, &pool_0_id); @@ -333,11 +340,36 @@ mod tests { let (_, usdc_token_client) = create_usdc_token(&e, &backstop_id, &bombadil); usdc_token_client.mint(&samwise, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + mock_pool_factory_client.set_pool(&pool_0_id); + e.as_contract(&backstop_id, || { execute_donate_usdc(&e, &samwise, &pool_0_id, -30_0000000); }); } + #[test] + #[should_panic(expected = "Error(Contract, #10)")] + fn test_execute_donate_usdc_not_pool() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + e.budget().reset_unlimited(); + + let backstop_id = create_backstop(&e); + let pool_0_id = Address::random(&e); + let bombadil = Address::random(&e); + let samwise = Address::random(&e); + + let (_, usdc_token_client) = create_usdc_token(&e, &backstop_id, &bombadil); + usdc_token_client.mint(&samwise, &100_0000000); + + create_mock_pool_factory(&e, &backstop_id); + + e.as_contract(&backstop_id, || { + execute_donate_usdc(&e, &samwise, &pool_0_id, 30_0000000); + }); + } + #[test] fn test_execute_gulp_usdc() { let e = Env::default(); @@ -355,6 +387,9 @@ mod tests { let (blnd_token, blnd_token_client) = create_blnd_token(&e, &backstop_id, &bombadil); blnd_token_client.mint(&samwise, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + mock_pool_factory_client.set_pool(&pool_0_id); + let (comet_id, comet_client) = create_comet_lp_pool(&e, &bombadil, &blnd_token, &usdc_token); diff --git a/backstop/src/backstop/mod.rs b/backstop/src/backstop/mod.rs index 1c9ac818..87fbc736 100644 --- a/backstop/src/backstop/mod.rs +++ b/backstop/src/backstop/mod.rs @@ -12,7 +12,8 @@ pub use withdrawal::{execute_dequeue_withdrawal, execute_queue_withdrawal, execu mod pool; pub use pool::{ - load_pool_backstop_data, require_is_from_pool_factory, PoolBackstopData, PoolBalance, + load_pool_backstop_data, require_is_from_pool_factory, require_pool_above_threshold, + PoolBackstopData, PoolBalance, }; mod user; diff --git a/backstop/src/backstop/pool.rs b/backstop/src/backstop/pool.rs index c23a1a74..0b1d8c01 100644 --- a/backstop/src/backstop/pool.rs +++ b/backstop/src/backstop/pool.rs @@ -38,16 +38,57 @@ pub fn load_pool_backstop_data(e: &Env, address: &Address) -> PoolBackstopData { } } -/// Verify the pool address was deployed by the Pool Factory +/// Verify the pool address was deployed by the Pool Factory. /// -/// Panics if the pool address cannot be verified -pub fn require_is_from_pool_factory(e: &Env, address: &Address) { - let pool_factory_client = PoolFactoryClient::new(e, &storage::get_pool_factory(e)); - if !pool_factory_client.is_pool(address) { - panic_with_error!(e, BackstopError::NotPool); +/// If the pool has an outstanding balance, it is assumed that it was verified before. +/// +/// ### Arguments +/// * `address` - The pool address to verify +/// * `balance` - The balance of the pool. A balance of 0 indicates the pool has not been initialized. +/// +/// ### Panics +/// If the pool address cannot be verified +pub fn require_is_from_pool_factory(e: &Env, address: &Address, balance: i128) { + if balance == 0 { + let pool_factory_client = PoolFactoryClient::new(e, &storage::get_pool_factory(e)); + if !pool_factory_client.is_pool(address) { + panic_with_error!(e, BackstopError::NotPool); + } } } +/// TODO: Duplicated from pool/pool/status.rs. Can this be moved to a common location? +/// +/// Calculate the threshold for the pool's backstop balance +/// +/// Returns true if the pool's backstop balance is above the threshold +/// NOTE: The calculation is the percentage^5 to simplify the calculation of the pools product constant. +/// Some useful calculation results: +/// - greater than 1 = 100+% +/// - 1_0000000 = 100% +/// - 0_0000100 = ~10% +/// - 0_0000003 = ~5% +/// - 0_0000000 = ~0-4% +pub fn require_pool_above_threshold(pool_backstop_data: &PoolBackstopData) -> bool { + // @dev: Calculation for pools product constant of underlying will often overflow i128 + // so saturating mul is used. This is safe because the threshold is below i128::MAX and the + // protocol does not need to differentiate between pools over the threshold product constant. + // The calculation is: + // - Threshold % = (bal_blnd^4 * bal_usdc) / PC^5 such that PC is 200k + let threshold_pc = 320_000_000_000_000_000_000_000_000i128; // 3.2e26 (200k^5) + // floor balances to nearest full unit and calculate saturated pool product constant + // and scale to SCALAR_7 to get final division result in SCALAR_7 points + let bal_blnd = pool_backstop_data.blnd / SCALAR_7; + let bal_usdc = pool_backstop_data.usdc / SCALAR_7; + let saturating_pool_pc = bal_blnd + .saturating_mul(bal_blnd) + .saturating_mul(bal_blnd) + .saturating_mul(bal_blnd) + .saturating_mul(bal_usdc) + .saturating_mul(SCALAR_7); // 10^7 * 10^7 + saturating_pool_pc / threshold_pc >= 1_0000000 +} + /// The pool's backstop balances #[derive(Clone)] #[contracttype] @@ -58,20 +99,11 @@ pub struct PoolBalance { } impl PoolBalance { - #[allow(clippy::should_implement_trait)] - pub fn default() -> PoolBalance { - PoolBalance { - shares: 0, - tokens: 0, - q4w: 0, - } - } - /// Convert a token balance to a share balance based on the current pool state /// /// ### Arguments /// * `tokens` - the token balance to convert - pub fn convert_to_shares(&mut self, tokens: i128) -> i128 { + pub fn convert_to_shares(&self, tokens: i128) -> i128 { if self.shares == 0 { return tokens; } @@ -85,7 +117,7 @@ impl PoolBalance { /// /// ### Arguments /// * `shares` - the pool share balance to convert - pub fn convert_to_tokens(&mut self, shares: i128) -> i128 { + pub fn convert_to_tokens(&self, shares: i128) -> i128 { if self.shares == 0 { return shares; } @@ -95,8 +127,15 @@ impl PoolBalance { .unwrap_optimized() } + /// Determine the amount of effective tokens (not queued for withdrawal) in the pool + pub fn non_queued_tokens(&self) -> i128 { + self.tokens - self.convert_to_tokens(self.q4w) + } + /// Deposit tokens and shares into the pool /// + /// If this is the first time + /// /// ### Arguments /// * `tokens` - The amount of tokens to add /// * `shares` - The amount of shares to add @@ -188,7 +227,22 @@ mod tests { mock_pool_factory.set_pool(&pool_address); e.as_contract(&backstop_address, || { - require_is_from_pool_factory(&e, &pool_address); + require_is_from_pool_factory(&e, &pool_address, 0); + assert!(true); + }); + } + + #[test] + fn test_require_is_from_pool_factory_skips_if_balance() { + let e = Env::default(); + + let backstop_address = create_backstop(&e); + let pool_address = Address::random(&e); + + // don't initialize factory to force failure if pool_address is checked + + e.as_contract(&backstop_address, || { + require_is_from_pool_factory(&e, &pool_address, 1); assert!(true); }); } @@ -206,16 +260,82 @@ mod tests { mock_pool_factory.set_pool(&pool_address); e.as_contract(&backstop_address, || { - require_is_from_pool_factory(&e, ¬_pool_address); + require_is_from_pool_factory(&e, ¬_pool_address, 0); assert!(false); }); } + /********** require_pool_above_threshold **********/ + + #[test] + fn test_require_pool_above_threshold_under() { + let e = Env::default(); + e.budget().reset_unlimited(); + + let pool_backstop_data = PoolBackstopData { + blnd: 300_000_0000000, + q4w_pct: 0, + tokens: 20_000_0000000, + usdc: 25_000_0000000, + }; // ~91.2% threshold + + let result = require_pool_above_threshold(&pool_backstop_data); + assert!(!result); + } + + #[test] + fn test_require_pool_above_threshold_zero() { + let e = Env::default(); + e.budget().reset_unlimited(); + + let pool_backstop_data = PoolBackstopData { + blnd: 5_000_0000000, + q4w_pct: 0, + tokens: 500_0000000, + usdc: 1_000_0000000, + }; // ~3.6% threshold - rounds to zero in calc + + let result = require_pool_above_threshold(&pool_backstop_data); + assert!(!result); + } + + #[test] + fn test_require_pool_above_threshold_over() { + let e = Env::default(); + e.budget().reset_unlimited(); + + let pool_backstop_data = PoolBackstopData { + blnd: 364_643_0000000, + q4w_pct: 0, + tokens: 15_000_0000000, + usdc: 18_100_0000000, + }; // 100% threshold + + let result = require_pool_above_threshold(&pool_backstop_data); + assert!(result); + } + + #[test] + fn test_require_pool_above_threshold_saturates() { + let e = Env::default(); + e.budget().reset_unlimited(); + + let pool_backstop_data = PoolBackstopData { + blnd: 50_000_000_0000000, + q4w_pct: 0, + tokens: 999_999_0000000, + usdc: 10_000_000_0000000, + }; // 181x threshold + + let result = require_pool_above_threshold(&pool_backstop_data); + assert!(result); + } + /********** Logic **********/ #[test] fn test_convert_to_shares_no_shares() { - let mut pool_balance = PoolBalance { + let pool_balance = PoolBalance { shares: 0, tokens: 0, q4w: 0, @@ -228,7 +348,7 @@ mod tests { #[test] fn test_convert_to_shares() { - let mut pool_balance = PoolBalance { + let pool_balance = PoolBalance { shares: 80321, tokens: 103302, q4w: 0, @@ -241,7 +361,7 @@ mod tests { #[test] fn test_convert_to_tokens_no_shares() { - let mut pool_balance = PoolBalance { + let pool_balance = PoolBalance { shares: 0, tokens: 0, q4w: 0, @@ -254,7 +374,7 @@ mod tests { #[test] fn test_convert_to_tokens() { - let mut pool_balance = PoolBalance { + let pool_balance = PoolBalance { shares: 80321, tokens: 103302, q4w: 0, diff --git a/backstop/src/backstop/withdrawal.rs b/backstop/src/backstop/withdrawal.rs index ba936c42..502e7ebf 100644 --- a/backstop/src/backstop/withdrawal.rs +++ b/backstop/src/backstop/withdrawal.rs @@ -76,7 +76,9 @@ mod tests { use crate::{ backstop::{execute_deposit, execute_donate}, - testutils::{assert_eq_vec_q4w, create_backstop, create_backstop_token}, + testutils::{ + assert_eq_vec_q4w, create_backstop, create_backstop_token, create_mock_pool_factory, + }, }; use super::*; @@ -94,6 +96,9 @@ mod tests { let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); backstop_token_client.mint(&samwise, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_address); + // setup pool with deposits e.as_contract(&backstop_address, || { execute_deposit(&e, &samwise, &pool_address, 100_0000000); @@ -151,6 +156,9 @@ mod tests { let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); backstop_token_client.mint(&samwise, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_address); + // setup pool with deposits e.as_contract(&backstop_address, || { execute_deposit(&e, &samwise, &pool_address, 100_0000000); @@ -185,6 +193,9 @@ mod tests { let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); backstop_token_client.mint(&samwise, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_address); + // queue shares for withdraw e.as_contract(&backstop_address, || { execute_deposit(&e, &samwise, &pool_address, 75_0000000); @@ -249,6 +260,9 @@ mod tests { let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); backstop_token_client.mint(&samwise, &100_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_address); + // queue shares for withdraw e.as_contract(&backstop_address, || { execute_deposit(&e, &samwise, &pool_address, 75_0000000); @@ -297,6 +311,9 @@ mod tests { let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); backstop_token_client.mint(&samwise, &150_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_address); + e.ledger().set(LedgerInfo { protocol_version: 20, sequence_number: 200, @@ -360,6 +377,9 @@ mod tests { let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); backstop_token_client.mint(&samwise, &150_0000000); + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_address); + e.ledger().set(LedgerInfo { protocol_version: 20, sequence_number: 200, diff --git a/backstop/src/contract.rs b/backstop/src/contract.rs index 0e0b4c1d..cdfb9f6a 100644 --- a/backstop/src/contract.rs +++ b/backstop/src/contract.rs @@ -1,5 +1,6 @@ use crate::{ backstop::{self, load_pool_backstop_data, PoolBackstopData, UserBalance, Q4W}, + dependencies::EmitterClient, emissions, errors::BackstopError, storage, @@ -18,8 +19,11 @@ pub struct BackstopContract; pub trait Backstop { /// Initialize the backstop /// + /// This function requires that the Emitter has already been initialized + /// /// ### Arguments /// * `backstop_token` - The backstop token ID - an LP token with the pair BLND:USDC + /// * `emitter` - The Emitter contract ID /// * `blnd_token` - The BLND token ID /// * `usdc_token` - The USDC token ID /// * `pool_factory` - The pool factory ID @@ -30,6 +34,7 @@ pub trait Backstop { fn initialize( e: Env, backstop_token: Address, + emitter: Address, blnd_token: Address, usdc_token: Address, pool_factory: Address, @@ -96,8 +101,8 @@ pub trait Backstop { /********** Emissions **********/ - /// Update the backstop for the next emissions cycle from the Emitter - fn update_emission_cycle(e: Env); + /// Consume emissions from the Emitter and distribute them to backstops and pools in the reward zone + fn gulp_emissions(e: Env); /// Add a pool to the reward zone, and if the reward zone is full, a pool to remove /// @@ -109,9 +114,8 @@ pub trait Backstop { /// If the pool to remove has more tokens, or if distribution occurred in the last 48 hours fn add_reward(e: Env, to_add: Address, to_remove: Address); - /// Fetch the EPS (emissions per second) and expiration for the current distribution window of a pool - /// in a tuple where (EPS, expiration) - fn pool_eps(e: Env, pool_address: Address) -> (i128, u64); + /// Consume the emissions for a pool and approve + fn gulp_pool_emissions(e: Env, pool_address: Address) -> i128; /// Claim backstop deposit emissions from a list of pools for `from` /// @@ -126,8 +130,8 @@ pub trait Backstop { /// If an invalid pool address is included fn claim(e: Env, from: Address, pool_addresses: Vec
, to: Address) -> i128; - /// Fetch the drop list - fn drop_list(e: Env) -> Map; + /// Drop initial BLND to a list of addresses through the emitter + fn drop(e: Env); /********** Fund Management *********/ @@ -196,6 +200,7 @@ impl Backstop for BackstopContract { fn initialize( e: Env, backstop_token: Address, + emitter: Address, usdc_token: Address, blnd_token: Address, pool_factory: Address, @@ -211,6 +216,14 @@ impl Backstop for BackstopContract { storage::set_usdc_token(&e, &usdc_token); storage::set_pool_factory(&e, &pool_factory); storage::set_drop_list(&e, &drop_list); + storage::set_emitter(&e, &emitter); + + // fetch last distribution time from emitter + // NOTE: For a replacement backstop, this must be fetched after the swap is completed, but this is + // a shortcut for the first backstop. + let last_distribution_time = + EmitterClient::new(&e, &emitter).get_last_distro(&e.current_contract_address()); + storage::set_last_distribution_time(&e, &last_distribution_time); } /********** Core **********/ @@ -280,9 +293,12 @@ impl Backstop for BackstopContract { /********** Emissions **********/ - fn update_emission_cycle(e: Env) { + fn gulp_emissions(e: Env) { storage::bump_instance(&e); - emissions::update_emission_cycle(&e); + let new_tokens_emitted = emissions::gulp_emissions(&e); + + e.events() + .publish((Symbol::new(&e, "gulp_emissions"),), new_tokens_emitted); } fn add_reward(e: Env, to_add: Address, to_remove: Address) { @@ -293,11 +309,10 @@ impl Backstop for BackstopContract { .publish((Symbol::new(&e, "rw_zone"),), (to_add, to_remove)); } - fn pool_eps(e: Env, pool_address: Address) -> (i128, u64) { - ( - storage::get_pool_eps(&e, &pool_address), - storage::get_next_emission_cycle(&e), - ) + fn gulp_pool_emissions(e: Env, pool_address: Address) -> i128 { + storage::bump_instance(&e); + pool_address.require_auth(); + emissions::gulp_pool_emissions(&e, &pool_address) } fn claim(e: Env, from: Address, pool_addresses: Vec
, to: Address) -> i128 { @@ -310,8 +325,8 @@ impl Backstop for BackstopContract { amount } - fn drop_list(e: Env) -> Map { - storage::get_drop_list(&e) + fn drop(e: Env) { + EmitterClient::new(&e, &storage::get_emitter(&e)).drop(&storage::get_drop_list(&e)) } /********** Fund Management *********/ diff --git a/backstop/src/dependencies/emitter.rs b/backstop/src/dependencies/emitter.rs new file mode 100644 index 00000000..2ee529dd --- /dev/null +++ b/backstop/src/dependencies/emitter.rs @@ -0,0 +1,3 @@ +use soroban_sdk::contractimport; + +contractimport!(file = "../target/wasm32-unknown-unknown/release/emitter.wasm"); diff --git a/backstop/src/dependencies/mod.rs b/backstop/src/dependencies/mod.rs index 53892d25..89463a25 100644 --- a/backstop/src/dependencies/mod.rs +++ b/backstop/src/dependencies/mod.rs @@ -5,3 +5,6 @@ mod comet; pub use comet::Client as CometClient; #[cfg(any(test, feature = "testutils"))] pub use comet::WASM as COMET_WASM; + +mod emitter; +pub use emitter::Client as EmitterClient; diff --git a/backstop/src/emissions/distributor.rs b/backstop/src/emissions/distributor.rs index a75674c5..d99a722d 100644 --- a/backstop/src/emissions/distributor.rs +++ b/backstop/src/emissions/distributor.rs @@ -1,3 +1,5 @@ +//! Methods for distributing backstop emissions to depositors + use cast::i128; use fixed_point_math::FixedPoint; use soroban_sdk::{unwrap::UnwrapOptimized, Address, Env}; @@ -6,6 +8,7 @@ use crate::{ backstop::{PoolBalance, UserBalance}, constants::SCALAR_7, storage::{self, BackstopEmissionsData, UserEmissionData}, + BackstopEmissionConfig, }; /// Update the backstop emissions index for the user and pool @@ -33,10 +36,23 @@ pub fn update_emission_data( pool_id: &Address, pool_balance: &PoolBalance, ) -> Option { - let emis_config = match storage::get_backstop_emis_config(e, pool_id) { - Some(res) => res, + match storage::get_backstop_emis_config(e, pool_id) { + Some(config) => Some(update_emission_data_with_config( + e, + pool_id, + pool_balance, + &config, + )), None => return None, // no emission exist, no update is required - }; + } +} + +pub fn update_emission_data_with_config( + e: &Env, + pool_id: &Address, + pool_balance: &PoolBalance, + emis_config: &BackstopEmissionConfig, +) -> BackstopEmissionsData { let emis_data = storage::get_backstop_emis_data(e, pool_id).unwrap_optimized(); // exists if config is written to if emis_data.last_time >= emis_config.expiration @@ -45,7 +61,7 @@ pub fn update_emission_data( || pool_balance.shares == 0 { // emis_data already updated or expired - return Some(emis_data); + return emis_data; } let max_timestamp = if e.ledger().timestamp() > emis_config.expiration { @@ -62,7 +78,7 @@ pub fn update_emission_data( last_time: e.ledger().timestamp(), }; storage::set_backstop_emis_data(e, pool_id, &new_data); - Some(new_data) + new_data } fn update_user_emissions( @@ -161,7 +177,7 @@ mod tests { accrued: 3, }; e.as_contract(&backstop_id, || { - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 7 * 24 * 60 * 60)); + storage::set_last_distribution_time(&e, &BACKSTOP_EPOCH); storage::set_backstop_emis_config(&e, &pool_1, &backstop_emissions_config); storage::set_backstop_emis_data(&e, &pool_1, &backstop_emissions_data); storage::set_user_emis_data(&e, &pool_1, &samwise, &user_emissions_data); @@ -210,7 +226,7 @@ mod tests { let samwise = Address::random(&e); e.as_contract(&backstop_id, || { - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 7 * 24 * 60 * 60)); + storage::set_last_distribution_time(&e, &BACKSTOP_EPOCH); let pool_balance = PoolBalance { shares: 150_0000000, @@ -265,7 +281,7 @@ mod tests { accrued: 3, }; e.as_contract(&backstop_id, || { - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 7 * 24 * 60 * 60)); + storage::set_last_distribution_time(&e, &BACKSTOP_EPOCH); storage::set_backstop_emis_config(&e, &pool_1, &backstop_emissions_config); storage::set_backstop_emis_data(&e, &pool_1, &backstop_emissions_data); storage::set_user_emis_data(&e, &pool_1, &samwise, &user_emissions_data); @@ -322,7 +338,7 @@ mod tests { last_time: BACKSTOP_EPOCH, }; e.as_contract(&backstop_id, || { - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 7 * 24 * 60 * 60)); + storage::set_last_distribution_time(&e, &BACKSTOP_EPOCH); storage::set_backstop_emis_config(&e, &pool_1, &backstop_emissions_config); storage::set_backstop_emis_data(&e, &pool_1, &backstop_emissions_data); @@ -378,7 +394,7 @@ mod tests { last_time: BACKSTOP_EPOCH, }; e.as_contract(&backstop_id, || { - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 7 * 24 * 60 * 60)); + storage::set_last_distribution_time(&e, &BACKSTOP_EPOCH); storage::set_backstop_emis_config(&e, &pool_1, &backstop_emissions_config); storage::set_backstop_emis_data(&e, &pool_1, &backstop_emissions_data); @@ -437,7 +453,7 @@ mod tests { accrued: 3, }; e.as_contract(&backstop_id, || { - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 7 * 24 * 60 * 60)); + storage::set_last_distribution_time(&e, &BACKSTOP_EPOCH); storage::set_backstop_emis_config(&e, &pool_1, &backstop_emissions_config); storage::set_backstop_emis_data(&e, &pool_1, &backstop_emissions_data); storage::set_user_emis_data(&e, &pool_1, &samwise, &user_emissions_data); diff --git a/backstop/src/emissions/manager.rs b/backstop/src/emissions/manager.rs index 6fa74477..755d80e4 100644 --- a/backstop/src/emissions/manager.rs +++ b/backstop/src/emissions/manager.rs @@ -4,12 +4,15 @@ use sep_41_token::TokenClient; use soroban_sdk::{panic_with_error, unwrap::UnwrapOptimized, vec, Address, Env, Vec}; use crate::{ + backstop::{load_pool_backstop_data, require_pool_above_threshold}, constants::{BACKSTOP_EPOCH, SCALAR_7}, + dependencies::EmitterClient, errors::BackstopError, storage::{self, BackstopEmissionConfig, BackstopEmissionsData}, + PoolBalance, }; -use super::update_emission_data; +use super::distributor::update_emission_data_with_config; /// Add a pool to the reward zone. If the reward zone is full, attempt to swap it with the pool to remove. pub fn add_to_reward_zone(e: &Env, to_add: Address, to_remove: Address) { @@ -21,33 +24,33 @@ pub fn add_to_reward_zone(e: &Env, to_add: Address, to_remove: Address) { panic_with_error!(e, BackstopError::BadRequest); } + // enusre to_add has met the minimum backstop deposit threshold + // NOTE: "to_add" can only carry a pool balance if it is a deployed pool from the factory + let pool_data = load_pool_backstop_data(e, &to_add); + if !require_pool_above_threshold(&pool_data) { + panic_with_error!(e, BackstopError::InvalidRewardZoneEntry); + } + if max_rz_len > i128(reward_zone.len()) { - // there is room in the reward zone. Add whatever - // TODO: Once there is a defined limit of "backstop minimum", ensure it is reached! + // there is room in the reward zone. Add "to_add". reward_zone.push_front(to_add.clone()); } else { - // don't allow rz modifications within 48 hours of the start of an emission cycle - // if pools don't adopt their emissions within this time frame and get swapped, the tokens will be lost - let next_distribution = storage::get_next_emission_cycle(e); - if next_distribution != 0 && e.ledger().timestamp() < next_distribution - 5 * 24 * 60 * 60 { - panic_with_error!(e, BackstopError::BadRequest); - } - - // attempt to swap the "to_remove" - // TODO: Once there is a defined limit of "backstop minimum", ensure it is reached! - if storage::get_pool_balance(e, &to_add).tokens - <= storage::get_pool_balance(e, &to_remove).tokens - { - panic_with_error!(e, BackstopError::InvalidRewardZoneEntry); - } - // swap to_add for to_remove let to_remove_index = reward_zone.first_index_of(to_remove.clone()); match to_remove_index { Some(idx) => { + // verify distribute was run recently to prevent "to_remove" from losing excess emissions + // @dev: resource constraints prevent us from distributing on reward zone changes + let last_distribution = storage::get_last_distribution_time(e); + if last_distribution < e.ledger().timestamp() - 24 * 60 * 60 { + panic_with_error!(e, BackstopError::BadRequest); + } + + // Verify "to_add" has a higher backstop deposit that "to_remove" + if pool_data.tokens <= storage::get_pool_balance(e, &to_remove).tokens { + panic_with_error!(e, BackstopError::InvalidRewardZoneEntry); + } reward_zone.set(idx, to_add.clone()); - storage::set_pool_eps(e, &to_remove, &0); - // emissions data is not updated. Emissions will be set on the next emission cycle } None => panic_with_error!(e, BackstopError::InvalidRewardZoneEntry), } @@ -56,80 +59,112 @@ pub fn add_to_reward_zone(e: &Env, to_add: Address, to_remove: Address) { storage::set_reward_zone(e, &reward_zone); } -/// Update the backstop for the next emission cycle from the Emitter +/// Assign emissions from the Emitter to backstops and pools in the reward zone #[allow(clippy::zero_prefixed_literal)] -pub fn update_emission_cycle(e: &Env) { - if e.ledger().timestamp() < storage::get_next_emission_cycle(e) { +pub fn gulp_emissions(e: &Env) -> i128 { + let emitter = storage::get_emitter(e); + let emitter_last_distribution = + EmitterClient::new(&e, &emitter).get_last_distro(&e.current_contract_address()); + let last_distribution = storage::get_last_distribution_time(e); + + // ensure enough time has passed between the last emitter distribution and gulp_emissions + // to prevent excess rounding issues + if emitter_last_distribution <= (last_distribution + 60 * 60) { panic_with_error!(e, BackstopError::BadRequest); } - let next_distribution = e.ledger().timestamp() + 7 * 24 * 60 * 60; - storage::set_next_emission_cycle(e, &next_distribution); + storage::set_last_distribution_time(e, &emitter_last_distribution); + let new_emissions = i128(emitter_last_distribution - last_distribution) * SCALAR_7; // emitter releases 1 token per second + let total_backstop_emissions = new_emissions + .fixed_mul_floor(0_7000000, SCALAR_7) + .unwrap_optimized(); + let total_pool_emissions = new_emissions + .fixed_mul_floor(0_3000000, SCALAR_7) + .unwrap_optimized(); let reward_zone = storage::get_reward_zone(e); let rz_len = reward_zone.len(); - let mut rz_tokens: Vec = vec![e]; + let mut rz_balance: Vec = vec![e]; // TODO: Potential to assume optimization of backstop token balances ~= RZ tokens // However, linear iteration over the RZ will still occur // fetch total tokens of BLND in the reward zone - let mut total_tokens: i128 = 0; + let mut total_non_queued_tokens: i128 = 0; for rz_pool_index in 0..rz_len { let rz_pool = reward_zone.get(rz_pool_index).unwrap_optimized(); - let mut pool_balance = storage::get_pool_balance(e, &rz_pool); - let net_deposits = - pool_balance.tokens.clone() - pool_balance.convert_to_tokens(pool_balance.q4w.clone()); - rz_tokens.push_back(net_deposits); - total_tokens += net_deposits; + let pool_balance = storage::get_pool_balance(e, &rz_pool); + total_non_queued_tokens += pool_balance.non_queued_tokens(); + rz_balance.push_back(pool_balance); } - let blnd_token_client = TokenClient::new(e, &storage::get_blnd_token(e)); // store pools EPS and distribute emissions to backstop depositors for rz_pool_index in 0..rz_len { let rz_pool = reward_zone.get(rz_pool_index).unwrap_optimized(); - let cur_pool_tokens = rz_tokens.pop_front_unchecked(); - let share = cur_pool_tokens - .fixed_div_floor(total_tokens, SCALAR_7) + let cur_pool_balance = rz_balance.pop_front_unchecked(); + let cur_pool_non_queued_tokens = cur_pool_balance.non_queued_tokens(); + let share = cur_pool_non_queued_tokens + .fixed_div_floor(total_non_queued_tokens, SCALAR_7) .unwrap_optimized(); // store pool EPS and distribute pool's emissions via allowances to pool - let pool_eps = share - .fixed_mul_floor(0_3000000, SCALAR_7) + let new_pool_emissions = share + .fixed_mul_floor(total_pool_emissions, SCALAR_7) .unwrap_optimized(); - let new_pool_emissions = pool_eps * 7 * 24 * 60 * 60; - let current_allowance = - blnd_token_client.allowance(&e.current_contract_address(), &rz_pool); - blnd_token_client.approve( - &e.current_contract_address(), - &rz_pool, - &(current_allowance + new_pool_emissions), - &(e.ledger().sequence() + 17_280 * 30), // ~30 days: TODO: check phase 1 limits - ); - storage::set_pool_eps(e, &rz_pool, &pool_eps); + let current_emissions = storage::get_pool_emissions(e, &rz_pool); + storage::set_pool_emissions(e, &rz_pool, current_emissions + new_pool_emissions); // distribute backstop depositor emissions - let pool_backstop_eps = share - .fixed_mul_floor(0_7000000, SCALAR_7) + let new_pool_backstop_tokens = share + .fixed_mul_floor(total_backstop_emissions, SCALAR_7) .unwrap_optimized(); - set_backstop_emission_config( - e, - &rz_pool, - u64(pool_backstop_eps).unwrap_optimized(), - next_distribution, - ); + set_backstop_emission_config(e, &rz_pool, &cur_pool_balance, new_pool_backstop_tokens); + } + new_emissions +} + +/// Consume pool emissions approve them to be transferred by the pool +pub fn gulp_pool_emissions(e: &Env, pool_id: &Address) -> i128 { + let pool_emissions = storage::get_pool_emissions(e, pool_id); + if pool_emissions == 0 { + panic_with_error!(e, BackstopError::BadRequest); } + + let blnd_token_client = TokenClient::new(e, &storage::get_blnd_token(e)); + let current_allowance = blnd_token_client.allowance(&e.current_contract_address(), pool_id); + let new_tokens = current_allowance + pool_emissions; + let new_seq = e.ledger().sequence() + 17_280 * 30; // ~30 days: TODO: check phase 1 limits + blnd_token_client.approve( + &e.current_contract_address(), + pool_id, + &new_tokens, + &new_seq, // ~30 days: TODO: check phase 1 limits + ); + storage::set_pool_emissions(e, pool_id, 0); + pool_emissions } /// Set a new EPS for the backstop -pub fn set_backstop_emission_config(e: &Env, pool_id: &Address, eps: u64, expiration: u64) { - if storage::has_backstop_emis_config(e, pool_id) { +pub fn set_backstop_emission_config( + e: &Env, + pool_id: &Address, + pool_balance: &PoolBalance, + new_tokens: i128, +) { + let mut tokens_left_to_emit = new_tokens; + if let Some(emis_config) = storage::get_backstop_emis_config(e, pool_id) { // a previous config exists - update with old config before setting new EPS - let pool_balance = storage::get_pool_balance(e, pool_id); - let mut emission_data = update_emission_data(e, pool_id, &pool_balance).unwrap_optimized(); + let mut emission_data = + update_emission_data_with_config(e, pool_id, &pool_balance, &emis_config); if emission_data.last_time != e.ledger().timestamp() { // force the emission data to be updated to the current timestamp emission_data.last_time = e.ledger().timestamp(); storage::set_backstop_emis_data(e, pool_id, &emission_data); } + // determine the amount of tokens not emitted from the last config + if emis_config.expiration > e.ledger().timestamp() { + let time_since_last_emission = emis_config.expiration - e.ledger().timestamp(); + let tokens_since_last_emission = i128(emis_config.eps * time_since_last_emission); + tokens_left_to_emit += tokens_since_last_emission; + } } else { // first time the pool's backstop is receiving emissions - ensure data is written storage::set_backstop_emis_data( @@ -141,6 +176,8 @@ pub fn set_backstop_emission_config(e: &Env, pool_id: &Address, eps: u64, expira }, ); } + let expiration = e.ledger().timestamp() + 7 * 24 * 60 * 60; + let eps = u64(tokens_left_to_emit / (7 * 24 * 60 * 60)).unwrap_optimized(); let backstop_emis_config = BackstopEmissionConfig { expiration, eps }; storage::set_backstop_emis_config(e, pool_id, &backstop_emis_config); } @@ -157,13 +194,13 @@ mod tests { use crate::{ backstop::PoolBalance, storage::BackstopEmissionConfig, - testutils::{self, create_backstop}, + testutils::{create_backstop, create_blnd_token, create_emitter}, }; - /********** update_emission_cycle **********/ + /********** gulp_emissions **********/ #[test] - fn test_update_emission_cycle_happy_path() { + fn test_gulp_emissions() { let e = Env::default(); e.budget().reset_unlimited(); @@ -178,33 +215,54 @@ mod tests { max_entry_expiration: 2000000, }); - let bombadil = Address::random(&e); let backstop = create_backstop(&e); - let (_, blnd_token_client) = testutils::create_blnd_token(&e, &backstop, &bombadil); + let emitter_distro_time = BACKSTOP_EPOCH - 10; + create_emitter( + &e, + &backstop, + &Address::random(&e), + &Address::random(&e), + emitter_distro_time, + ); let pool_1 = Address::random(&e); let pool_2 = Address::random(&e); let pool_3 = Address::random(&e); let reward_zone: Vec
= vec![&e, pool_1.clone(), pool_2.clone(), pool_3.clone()]; + // setup pool 1 to have ongoing emissions let pool_1_emissions_config = BackstopEmissionConfig { - expiration: BACKSTOP_EPOCH, + expiration: BACKSTOP_EPOCH + 1000, eps: 0_1000000, }; let pool_1_emissions_data = BackstopEmissionsData { index: 887766, last_time: BACKSTOP_EPOCH - 12345, }; + + // setup pool 2 to have expired emissions + let pool_2_emissions_config = BackstopEmissionConfig { + expiration: BACKSTOP_EPOCH - 12345, + eps: 0_0500000, + }; + let pool_2_emissions_data = BackstopEmissionsData { + index: 453234, + last_time: BACKSTOP_EPOCH - 12345, + }; + // setup pool 3 to have no emissions e.as_contract(&backstop, || { - storage::set_next_emission_cycle(&e, &BACKSTOP_EPOCH); + storage::set_last_distribution_time(&e, &(emitter_distro_time - 7 * 24 * 60 * 60)); storage::set_reward_zone(&e, &reward_zone); storage::set_backstop_emis_config(&e, &pool_1, &pool_1_emissions_config); storage::set_backstop_emis_data(&e, &pool_1, &pool_1_emissions_data); + storage::set_pool_emissions(&e, &pool_1, 100_123_0000000); + storage::set_backstop_emis_config(&e, &pool_2, &pool_2_emissions_config); + storage::set_backstop_emis_data(&e, &pool_2, &pool_2_emissions_data); storage::set_pool_balance( &e, &pool_1, &PoolBalance { tokens: 300_000_0000000, - shares: 300_000_0000000, + shares: 200_000_0000000, q4w: 0, }, ); @@ -213,7 +271,7 @@ mod tests { &pool_2, &PoolBalance { tokens: 200_000_0000000, - shares: 200_000_0000000, + shares: 150_000_0000000, q4w: 0, }, ); @@ -222,18 +280,15 @@ mod tests { &pool_3, &PoolBalance { tokens: 500_000_0000000, - shares: 500_000_0000000, + shares: 600_000_0000000, q4w: 0, }, ); - blnd_token_client.approve(&backstop, &pool_1, &100_123_0000000, &1000000); + // blnd_token_client.approve(&backstop, &pool_1, &100_123_0000000, &1000000); - update_emission_cycle(&e); + gulp_emissions(&e); - assert_eq!( - storage::get_next_emission_cycle(&e), - BACKSTOP_EPOCH + 7 * 24 * 60 * 60 - ); + assert_eq!(storage::get_last_distribution_time(&e), emitter_distro_time); assert_eq!( storage::get_pool_balance(&e, &pool_1).tokens, 300_000_0000000 @@ -246,32 +301,22 @@ mod tests { storage::get_pool_balance(&e, &pool_3).tokens, 500_000_0000000 ); - assert_eq!(storage::get_pool_eps(&e, &pool_1), 0_0900000); - assert_eq!(storage::get_pool_eps(&e, &pool_2), 0_0600000); - assert_eq!(storage::get_pool_eps(&e, &pool_3), 0_1500000); - assert_eq!( - blnd_token_client.allowance(&backstop, &pool_1), - 154_555_0000000 - ); - assert_eq!( - blnd_token_client.allowance(&backstop, &pool_2), - 36_288_0000000 - ); - assert_eq!( - blnd_token_client.allowance(&backstop, &pool_3), - 90_720_0000000 - ); + assert_eq!(storage::get_pool_emissions(&e, &pool_1), 154_555_0000000); + assert_eq!(storage::get_pool_emissions(&e, &pool_2), 36_288_0000000); + assert_eq!(storage::get_pool_emissions(&e, &pool_3), 90_720_0000000); + + // validate backstop emissions let new_pool_1_config = storage::get_backstop_emis_config(&e, &pool_1).unwrap_optimized(); let new_pool_1_data = storage::get_backstop_emis_data(&e, &pool_1).unwrap_optimized(); - assert_eq!(new_pool_1_config.eps, 0_2100000); + assert_eq!(new_pool_1_config.eps, 0_2101653); assert_eq!( new_pool_1_config.expiration, BACKSTOP_EPOCH + 7 * 24 * 60 * 60 ); - // old config applied up to block timestamp - assert_eq!(new_pool_1_data.index, 928916); + assert_eq!(new_pool_1_data.index, 949491); assert_eq!(new_pool_1_data.last_time, BACKSTOP_EPOCH); + let new_pool_2_config = storage::get_backstop_emis_config(&e, &pool_2).unwrap_optimized(); let new_pool_2_data = storage::get_backstop_emis_data(&e, &pool_2).unwrap_optimized(); @@ -280,8 +325,9 @@ mod tests { new_pool_2_config.expiration, BACKSTOP_EPOCH + 7 * 24 * 60 * 60 ); - assert_eq!(new_pool_2_data.index, 0); + assert_eq!(new_pool_2_data.index, 453234); assert_eq!(new_pool_2_data.last_time, BACKSTOP_EPOCH); + let new_pool_3_config = storage::get_backstop_emis_config(&e, &pool_3).unwrap_optimized(); let new_pool_3_data = storage::get_backstop_emis_data(&e, &pool_3).unwrap_optimized(); @@ -297,8 +343,10 @@ mod tests { #[test] #[should_panic(expected = "Error(Contract, #1)")] - fn test_update_emission_cycle_too_early() { + fn test_gulp_emissions_too_soon() { let e = Env::default(); + e.budget().reset_unlimited(); + e.ledger().set(LedgerInfo { timestamp: BACKSTOP_EPOCH, protocol_version: 20, @@ -310,21 +358,54 @@ mod tests { max_entry_expiration: 2000000, }); - let backstop_id = create_backstop(&e); + let backstop = create_backstop(&e); + let emitter_distro_time = BACKSTOP_EPOCH - 10; + create_emitter( + &e, + &backstop, + &Address::random(&e), + &Address::random(&e), + emitter_distro_time, + ); let pool_1 = Address::random(&e); let pool_2 = Address::random(&e); let pool_3 = Address::random(&e); let reward_zone: Vec
= vec![&e, pool_1.clone(), pool_2.clone(), pool_3.clone()]; - e.as_contract(&backstop_id, || { - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 1)); + // setup pool 1 to have ongoing emissions + let pool_1_emissions_config = BackstopEmissionConfig { + expiration: BACKSTOP_EPOCH + 1000, + eps: 0_1000000, + }; + let pool_1_emissions_data = BackstopEmissionsData { + index: 887766, + last_time: BACKSTOP_EPOCH - 12345, + }; + + // setup pool 2 to have expired emissions + let pool_2_emissions_config = BackstopEmissionConfig { + expiration: BACKSTOP_EPOCH - 12345, + eps: 0_0500000, + }; + let pool_2_emissions_data = BackstopEmissionsData { + index: 453234, + last_time: BACKSTOP_EPOCH - 12345, + }; + // setup pool 3 to have no emissions + e.as_contract(&backstop, || { + storage::set_last_distribution_time(&e, &(emitter_distro_time - 59 * 60)); storage::set_reward_zone(&e, &reward_zone); + storage::set_backstop_emis_config(&e, &pool_1, &pool_1_emissions_config); + storage::set_backstop_emis_data(&e, &pool_1, &pool_1_emissions_data); + storage::set_pool_emissions(&e, &pool_1, 100_123_0000000); + storage::set_backstop_emis_config(&e, &pool_2, &pool_2_emissions_config); + storage::set_backstop_emis_data(&e, &pool_2, &pool_2_emissions_data); storage::set_pool_balance( &e, &pool_1, &PoolBalance { tokens: 300_000_0000000, - shares: 300_000_0000000, + shares: 200_000_0000000, q4w: 0, }, ); @@ -333,7 +414,7 @@ mod tests { &pool_2, &PoolBalance { tokens: 200_000_0000000, - shares: 200_000_0000000, + shares: 150_000_0000000, q4w: 0, }, ); @@ -342,12 +423,111 @@ mod tests { &pool_3, &PoolBalance { tokens: 500_000_0000000, - shares: 500_000_0000000, + shares: 600_000_0000000, q4w: 0, }, ); - update_emission_cycle(&e); + gulp_emissions(&e); + }); + } + + /********** gulp_pool_emissions **********/ + + #[test] + fn test_gulp_pool_emissions() { + let e = Env::default(); + e.budget().reset_unlimited(); + + e.ledger().set(LedgerInfo { + timestamp: BACKSTOP_EPOCH, + protocol_version: 20, + sequence_number: 0, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let backstop = create_backstop(&e); + let pool_1 = Address::random(&e); + let (_, blnd_token_client) = create_blnd_token(&e, &backstop, &bombadil); + + e.as_contract(&backstop, || { + storage::set_pool_emissions(&e, &pool_1, 100_123_0000000); + + gulp_pool_emissions(&e, &pool_1); + + assert_eq!(storage::get_pool_emissions(&e, &pool_1), 0); + assert_eq!( + blnd_token_client.allowance(&e.current_contract_address(), &pool_1), + 100_123_0000000 + ); + }); + } + + #[test] + fn test_gulp_pool_emissions_has_allowance() { + let e = Env::default(); + e.budget().reset_unlimited(); + + e.ledger().set(LedgerInfo { + timestamp: BACKSTOP_EPOCH, + protocol_version: 20, + sequence_number: 0, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let backstop = create_backstop(&e); + let pool_1 = Address::random(&e); + let (_, blnd_token_client) = create_blnd_token(&e, &backstop, &bombadil); + + e.as_contract(&backstop, || { + blnd_token_client.approve(&backstop, &pool_1, &1234567, &1000); + + storage::set_pool_emissions(&e, &pool_1, 123_0000000); + + gulp_pool_emissions(&e, &pool_1); + + assert_eq!(storage::get_pool_emissions(&e, &pool_1), 0); + assert_eq!( + blnd_token_client.allowance(&e.current_contract_address(), &pool_1), + 123_1234567 + ); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #1)")] + fn test_gulp_pool_emissions_no_emissions() { + let e = Env::default(); + e.budget().reset_unlimited(); + + e.ledger().set(LedgerInfo { + timestamp: BACKSTOP_EPOCH, + protocol_version: 20, + sequence_number: 0, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let bombadil = Address::random(&e); + let backstop = create_backstop(&e); + let pool_1 = Address::random(&e); + create_blnd_token(&e, &backstop, &bombadil); + + e.as_contract(&backstop, || { + gulp_pool_emissions(&e, &pool_1); }); } @@ -371,6 +551,58 @@ mod tests { let to_add = Address::random(&e); e.as_contract(&backstop_id, || { + storage::set_pool_balance( + &e, + &to_add, + &PoolBalance { + shares: 90_000_0000000, + tokens: 100_000_0000000, + q4w: 1_000_0000000, + }, + ); + storage::set_lp_token_val(&e, &(5_0000000, 0_1000000)); + + add_to_reward_zone( + &e, + to_add.clone(), + Address::from_contract_id(&BytesN::from_array(&e, &[0u8; 32])), + ); + let actual_rz = storage::get_reward_zone(&e); + let expected_rz: Vec
= vec![&e, to_add]; + assert_eq!(actual_rz, expected_rz); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #4)")] + fn test_add_to_rz_empty_pool_under_backstop_threshold() { + let e = Env::default(); + e.ledger().set(LedgerInfo { + timestamp: BACKSTOP_EPOCH, + protocol_version: 20, + sequence_number: 0, + base_reserve: 10, + network_id: Default::default(), + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let backstop_id = create_backstop(&e); + let to_add = Address::random(&e); + + e.as_contract(&backstop_id, || { + storage::set_pool_balance( + &e, + &to_add, + &PoolBalance { + shares: 100_000_0000000, + tokens: 75_000_0000000, + q4w: 1_000_0000000, + }, + ); + storage::set_lp_token_val(&e, &(5_0000000, 0_1000000)); + add_to_reward_zone( &e, to_add.clone(), @@ -414,6 +646,17 @@ mod tests { e.as_contract(&backstop_id, || { storage::set_reward_zone(&e, &reward_zone); + storage::set_pool_balance( + &e, + &to_add, + &PoolBalance { + shares: 90_000_0000000, + tokens: 100_000_0000000, + q4w: 1_000_0000000, + }, + ); + storage::set_lp_token_val(&e, &(5_0000000, 0_1000000)); + add_to_reward_zone( &e, to_add.clone(), @@ -457,6 +700,17 @@ mod tests { e.as_contract(&backstop_id, || { storage::set_reward_zone(&e, &reward_zone); + storage::set_pool_balance( + &e, + &to_add, + &PoolBalance { + shares: 90_000_0000000, + tokens: 100_000_0000000, + q4w: 1_000_0000000, + }, + ); + storage::set_lp_token_val(&e, &(5_0000000, 0_1000000)); + add_to_reward_zone( &e, to_add.clone(), @@ -498,31 +752,32 @@ mod tests { e.as_contract(&backstop_id, || { storage::set_reward_zone(&e, &reward_zone); - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 5 * 24 * 60 * 60)); - storage::set_pool_eps(&e, &to_remove, &1); + storage::set_last_distribution_time(&e, &(BACKSTOP_EPOCH - 1 * 24 * 60 * 60)); + storage::set_pool_emissions(&e, &to_remove, 1); storage::set_pool_balance( &e, &to_add, &PoolBalance { - shares: 50, - tokens: 100, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_001_0000000, + q4w: 1_000_0000000, }, ); storage::set_pool_balance( &e, &to_remove, &PoolBalance { - shares: 50, - tokens: 99, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_000_0000000, + q4w: 1_000_0000000, }, ); + storage::set_lp_token_val(&e, &(5_0000000, 0_1000000)); add_to_reward_zone(&e, to_add.clone(), to_remove.clone()); - let remove_eps = storage::get_pool_eps(&e, &to_remove); - assert_eq!(remove_eps, 0); + let remove_eps = storage::get_pool_emissions(&e, &to_remove); + assert_eq!(remove_eps, 1); let actual_rz = storage::get_reward_zone(&e); assert_eq!(actual_rz.len(), 10); reward_zone.set(7, to_add); @@ -563,35 +818,36 @@ mod tests { ]; e.as_contract(&backstop_id, || { - storage::set_reward_zone(&e, &reward_zone.clone()); - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 24 * 60 * 60)); - storage::set_pool_eps(&e, &to_remove, &1); + storage::set_reward_zone(&e, &reward_zone); + storage::set_last_distribution_time(&e, &(BACKSTOP_EPOCH - 1 * 24 * 60 * 60)); + storage::set_pool_emissions(&e, &to_remove, 1); storage::set_pool_balance( &e, &to_add, &PoolBalance { - shares: 50, - tokens: 100, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_000_0000000, + q4w: 1_000_0000000, }, ); storage::set_pool_balance( &e, &to_remove, &PoolBalance { - shares: 50, - tokens: 100, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_000_0000000, + q4w: 1_000_0000000, }, ); + storage::set_lp_token_val(&e, &(5_0000000, 0_1000000)); add_to_reward_zone(&e, to_add.clone(), to_remove); }); } #[test] - #[should_panic(expected = "Error(Contract, #4)")] - fn test_add_to_rz_to_remove_not_in_rz() { + #[should_panic(expected = "Error(Contract, #1)")] + fn test_add_to_rz_swap_distribution_too_long_ago() { let e = Env::default(); e.ledger().set(LedgerInfo { timestamp: BACKSTOP_EPOCH, @@ -616,41 +872,42 @@ mod tests { Address::random(&e), Address::random(&e), Address::random(&e), - Address::random(&e), + to_remove.clone(), // index 7 Address::random(&e), Address::random(&e), ]; e.as_contract(&backstop_id, || { storage::set_reward_zone(&e, &reward_zone); - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 24 * 60 * 60)); - storage::set_pool_eps(&e, &to_remove, &1); + storage::set_last_distribution_time(&e, &(BACKSTOP_EPOCH - 1 * 24 * 60 * 60 - 1)); + storage::set_pool_emissions(&e, &to_remove, 1); storage::set_pool_balance( &e, &to_add, &PoolBalance { - shares: 50, - tokens: 100, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_001_0000000, + q4w: 1_000_0000000, }, ); storage::set_pool_balance( &e, &to_remove, &PoolBalance { - shares: 50, - tokens: 99, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_000_0000000, + q4w: 1_000_0000000, }, ); + storage::set_lp_token_val(&e, &(5_0000000, 0_1000000)); add_to_reward_zone(&e, to_add.clone(), to_remove); }); } #[test] - #[should_panic(expected = "Error(Contract, #1)")] - fn test_add_to_rz_swap_too_soon_to_distribution() { + #[should_panic(expected = "Error(Contract, #4)")] + fn test_add_to_rz_to_remove_not_in_rz() { let e = Env::default(); e.ledger().set(LedgerInfo { timestamp: BACKSTOP_EPOCH, @@ -675,35 +932,36 @@ mod tests { Address::random(&e), Address::random(&e), Address::random(&e), - to_remove.clone(), // index 7 + Address::random(&e), Address::random(&e), Address::random(&e), ]; e.as_contract(&backstop_id, || { storage::set_reward_zone(&e, &reward_zone); - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 5 * 24 * 60 * 60 + 1)); - storage::set_pool_eps(&e, &to_remove, &1); + storage::set_last_distribution_time(&e, &(BACKSTOP_EPOCH - 24 * 60 * 60)); + storage::set_pool_emissions(&e, &to_remove, 1); storage::set_pool_balance( &e, &to_add, &PoolBalance { - shares: 50, - tokens: 100, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_001_0000000, + q4w: 1_000_0000000, }, ); storage::set_pool_balance( &e, &to_remove, &PoolBalance { - shares: 50, - tokens: 99, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_000_0000000, + q4w: 1_000_0000000, }, ); + storage::set_lp_token_val(&e, &(5_0000000, 0_1000000)); - add_to_reward_zone(&e, to_add, to_remove); + add_to_reward_zone(&e, to_add.clone(), to_remove); }); } @@ -741,26 +999,27 @@ mod tests { e.as_contract(&backstop_id, || { storage::set_reward_zone(&e, &reward_zone); - storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 5 * 24 * 60 * 60)); - storage::set_pool_eps(&e, &to_remove, &1); + storage::set_last_distribution_time(&e, &(BACKSTOP_EPOCH - 24 * 60 * 60)); + storage::set_pool_emissions(&e, &to_remove, 1); storage::set_pool_balance( &e, &to_add, &PoolBalance { - shares: 50, - tokens: 100, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_001_0000000, + q4w: 1_000_0000000, }, ); storage::set_pool_balance( &e, &to_remove, &PoolBalance { - shares: 50, - tokens: 99, - q4w: 0, + shares: 90_000_0000000, + tokens: 100_000_0000000, + q4w: 1_000_0000000, }, ); + storage::set_lp_token_val(&e, &(5_0000000, 0_1000000)); add_to_reward_zone(&e, to_add.clone(), to_remove.clone()); }); diff --git a/backstop/src/emissions/mod.rs b/backstop/src/emissions/mod.rs index 08dbf136..fa6c629a 100644 --- a/backstop/src/emissions/mod.rs +++ b/backstop/src/emissions/mod.rs @@ -5,4 +5,4 @@ mod distributor; pub use distributor::{update_emission_data, update_emissions}; mod manager; -pub use manager::{add_to_reward_zone, update_emission_cycle}; +pub use manager::{add_to_reward_zone, gulp_emissions, gulp_pool_emissions}; diff --git a/backstop/src/storage.rs b/backstop/src/storage.rs index 2e107f74..3501933c 100644 --- a/backstop/src/storage.rs +++ b/backstop/src/storage.rs @@ -1,5 +1,6 @@ use soroban_sdk::{ - contracttype, unwrap::UnwrapOptimized, vec, Address, Env, Map, Symbol, TryFromVal, Val, Vec, + contracttype, unwrap::UnwrapOptimized, vec, Address, Env, IntoVal, Map, Symbol, TryFromVal, + Val, Vec, }; use crate::backstop::{PoolBalance, UserBalance}; @@ -38,6 +39,16 @@ pub struct UserEmissionData { /********** Storage Key Types **********/ +const EMITTER_KEY: &str = "Emitter"; +const BACKSTOP_TOKEN_KEY: &str = "BToken"; +const POOL_FACTORY_KEY: &str = "PoolFact"; +const BLND_TOKEN_KEY: &str = "BLNDTkn"; +const USDC_TOKEN_KEY: &str = "USDCTkn"; +const LAST_DISTRO_KEY: &str = "LastDist"; +const REWARD_ZONE_KEY: &str = "RZ"; +const DROP_LIST_KEY: &str = "DropList"; +const LP_TOKEN_VAL_KEY: &str = "LPTknVal"; + #[derive(Clone)] #[contracttype] pub struct PoolUserKey { @@ -51,14 +62,10 @@ pub enum BackstopDataKey { UserBalance(PoolUserKey), PoolBalance(Address), PoolUSDC(Address), - NextEmis, - RewardZone, - PoolEPS(Address), + PoolEmis(Address), BEmisCfg(Address), BEmisData(Address), UEmisData(PoolUserKey), - DropList, - LPTknVal, } /**************************** @@ -73,14 +80,14 @@ pub fn bump_instance(e: &Env) { } /// Fetch an entry in persistent storage that has a default value if it doesn't exist -fn get_persistent_default>( +fn get_persistent_default, V: TryFromVal>( e: &Env, - key: &BackstopDataKey, + key: &K, default: V, bump_threshold: u32, bump_amount: u32, ) -> V { - if let Some(result) = e.storage().persistent().get::(key) { + if let Some(result) = e.storage().persistent().get::(key) { e.storage() .persistent() .bump(key, bump_threshold, bump_amount); @@ -92,11 +99,29 @@ fn get_persistent_default>( /********** External Contracts **********/ +/// Fetch the pool factory id +pub fn get_emitter(e: &Env) -> Address { + e.storage() + .instance() + .get::(&Symbol::new(e, EMITTER_KEY)) + .unwrap_optimized() +} + +/// Set the pool factory +/// +/// ### Arguments +/// * `pool_factory_id` - The ID of the pool factory +pub fn set_emitter(e: &Env, pool_factory_id: &Address) { + e.storage() + .instance() + .set::(&Symbol::new(e, EMITTER_KEY), pool_factory_id); +} + /// Fetch the pool factory id pub fn get_pool_factory(e: &Env) -> Address { e.storage() .instance() - .get::(&Symbol::new(e, "PoolFact")) + .get::(&Symbol::new(e, POOL_FACTORY_KEY)) .unwrap_optimized() } @@ -107,14 +132,14 @@ pub fn get_pool_factory(e: &Env) -> Address { pub fn set_pool_factory(e: &Env, pool_factory_id: &Address) { e.storage() .instance() - .set::(&Symbol::new(e, "PoolFact"), pool_factory_id); + .set::(&Symbol::new(e, POOL_FACTORY_KEY), pool_factory_id); } /// Fetch the BLND token id pub fn get_blnd_token(e: &Env) -> Address { e.storage() .instance() - .get::(&Symbol::new(e, "BLNDTkn")) + .get::(&Symbol::new(e, BLND_TOKEN_KEY)) .unwrap_optimized() } @@ -125,14 +150,14 @@ pub fn get_blnd_token(e: &Env) -> Address { pub fn set_blnd_token(e: &Env, blnd_token_id: &Address) { e.storage() .instance() - .set::(&Symbol::new(e, "BLNDTkn"), blnd_token_id); + .set::(&Symbol::new(e, BLND_TOKEN_KEY), blnd_token_id); } /// Fetch the USDC token id pub fn get_usdc_token(e: &Env) -> Address { e.storage() .instance() - .get::(&Symbol::new(e, "USDCTkn")) + .get::(&Symbol::new(e, USDC_TOKEN_KEY)) .unwrap_optimized() } @@ -143,20 +168,22 @@ pub fn get_usdc_token(e: &Env) -> Address { pub fn set_usdc_token(e: &Env, usdc_token_id: &Address) { e.storage() .instance() - .set::(&Symbol::new(e, "USDCTkn"), usdc_token_id); + .set::(&Symbol::new(e, USDC_TOKEN_KEY), usdc_token_id); } /// Fetch the backstop token id pub fn get_backstop_token(e: &Env) -> Address { e.storage() .instance() - .get::(&Symbol::new(e, "BckstpTkn")) + .get::(&Symbol::new(e, BACKSTOP_TOKEN_KEY)) .unwrap_optimized() } /// Checks if a backstop token is set for the backstop pub fn has_backstop_token(e: &Env) -> bool { - e.storage().instance().has(&Symbol::new(e, "BckstpTkn")) + e.storage() + .instance() + .has(&Symbol::new(e, BACKSTOP_TOKEN_KEY)) } /// Set the backstop token id @@ -166,7 +193,7 @@ pub fn has_backstop_token(e: &Env) -> bool { pub fn set_backstop_token(e: &Env, backstop_token_id: &Address) { e.storage() .instance() - .set::(&Symbol::new(e, "BckstpTkn"), backstop_token_id); + .set::(&Symbol::new(e, BACKSTOP_TOKEN_KEY), backstop_token_id); } /********** User Shares **********/ @@ -217,6 +244,16 @@ pub fn set_user_balance(e: &Env, pool: &Address, user: &Address, balance: &UserB /// * `pool` - The pool the deposit is associated with pub fn get_pool_balance(e: &Env, pool: &Address) -> PoolBalance { let key = BackstopDataKey::PoolBalance(pool.clone()); + // if let Some(result) = e.storage().persistent().get::(&key) { + // e.storage() + // .persistent() + // .bump(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); + // result + // } else { + // // @dev: Required to use "new" to require that "pool" was deployed from the factory + // PoolBalance::new(e, pool) + // } + get_persistent_default( e, &key, @@ -272,10 +309,10 @@ pub fn set_pool_usdc(e: &Env, pool: &Address, balance: &i128) { /********** Distribution / Reward Zone **********/ /// Get the timestamp of when the next emission cycle begins -pub fn get_next_emission_cycle(e: &Env) -> u64 { +pub fn get_last_distribution_time(e: &Env) -> u64 { get_persistent_default( e, - &BackstopDataKey::NextEmis, + &Symbol::new(e, LAST_DISTRO_KEY), 0u64, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED, @@ -286,12 +323,12 @@ pub fn get_next_emission_cycle(e: &Env) -> u64 { /// /// ### Arguments /// * `timestamp` - The timestamp the distribution window will open -pub fn set_next_emission_cycle(e: &Env, timestamp: &u64) { +pub fn set_last_distribution_time(e: &Env, timestamp: &u64) { e.storage() .persistent() - .set::(&BackstopDataKey::NextEmis, timestamp); + .set::(&Symbol::new(e, LAST_DISTRO_KEY), timestamp); e.storage().persistent().bump( - &BackstopDataKey::NextEmis, + &Symbol::new(e, LAST_DISTRO_KEY), LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED, ); @@ -303,7 +340,7 @@ pub fn set_next_emission_cycle(e: &Env, timestamp: &u64) { pub fn get_reward_zone(e: &Env) -> Vec
{ get_persistent_default( e, - &BackstopDataKey::RewardZone, + &Symbol::new(e, REWARD_ZONE_KEY), vec![e], LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED, @@ -317,9 +354,9 @@ pub fn get_reward_zone(e: &Env) -> Vec
{ pub fn set_reward_zone(e: &Env, reward_zone: &Vec
) { e.storage() .persistent() - .set::>(&BackstopDataKey::RewardZone, reward_zone); + .set::>(&Symbol::new(e, REWARD_ZONE_KEY), reward_zone); e.storage().persistent().bump( - &BackstopDataKey::RewardZone, + &Symbol::new(e, REWARD_ZONE_KEY), LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED, ); @@ -329,8 +366,8 @@ pub fn set_reward_zone(e: &Env, reward_zone: &Vec
) { /// /// ### Arguments /// * `pool` - The pool -pub fn get_pool_eps(e: &Env, pool: &Address) -> i128 { - let key = BackstopDataKey::PoolEPS(pool.clone()); +pub fn get_pool_emissions(e: &Env, pool: &Address) -> i128 { + let key = BackstopDataKey::PoolEmis(pool.clone()); get_persistent_default(e, &key, 0i128, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED) } @@ -338,12 +375,12 @@ pub fn get_pool_eps(e: &Env, pool: &Address) -> i128 { /// /// ### Arguments /// * `pool` - The pool -/// * `eps` - The eps being distributed to the pool -pub fn set_pool_eps(e: &Env, pool: &Address, eps: &i128) { - let key = BackstopDataKey::PoolEPS(pool.clone()); +/// * `emissions` - The eps being distributed to the pool +pub fn set_pool_emissions(e: &Env, pool: &Address, emissions: i128) { + let key = BackstopDataKey::PoolEmis(pool.clone()); e.storage() .persistent() - .set::(&key, eps); + .set::(&key, &emissions); e.storage() .persistent() .bump(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); @@ -357,7 +394,7 @@ pub fn set_pool_eps(e: &Env, pool: &Address, eps: &i128) { /// * `pool` - The pool pub fn get_backstop_emis_config(e: &Env, pool: &Address) -> Option { let key = BackstopDataKey::BEmisCfg(pool.clone()); - get_persistent_default::>( + get_persistent_default::>( e, &key, None, @@ -366,15 +403,6 @@ pub fn get_backstop_emis_config(e: &Env, pool: &Address) -> Option bool { - let key = BackstopDataKey::BEmisCfg(pool.clone()); - e.storage().persistent().has::(&key) -} - /// Set the pool's backstop emissions config /// /// ### Arguments @@ -397,7 +425,7 @@ pub fn set_backstop_emis_config( /// * `pool` - The pool pub fn get_backstop_emis_data(e: &Env, pool: &Address) -> Option { let key = BackstopDataKey::BEmisData(pool.clone()); - get_persistent_default::>( + get_persistent_default::>( e, &key, None, @@ -428,7 +456,7 @@ pub fn get_user_emis_data(e: &Env, pool: &Address, user: &Address) -> Option>( + get_persistent_default::>( e, &key, None, @@ -464,7 +492,7 @@ pub fn set_user_emis_data( pub fn get_drop_list(e: &Env) -> Map { e.storage() .temporary() - .get::>(&BackstopDataKey::DropList) + .get::>(&Symbol::new(&e, DROP_LIST_KEY)) .unwrap_optimized() } @@ -475,9 +503,9 @@ pub fn get_drop_list(e: &Env) -> Map { pub fn set_drop_list(e: &Env, drop_list: &Map) { e.storage() .temporary() - .set::>(&BackstopDataKey::DropList, drop_list); + .set::>(&Symbol::new(&e, DROP_LIST_KEY), drop_list); e.storage().temporary().bump( - &BackstopDataKey::DropList, + &Symbol::new(&e, DROP_LIST_KEY), LEDGER_THRESHOLD_USER, LEDGER_BUMP_USER, ); @@ -488,13 +516,13 @@ pub fn set_drop_list(e: &Env, drop_list: &Map) { /// Get the last updated token value for the LP pool pub fn get_lp_token_val(e: &Env) -> (i128, i128) { e.storage().persistent().bump( - &BackstopDataKey::LPTknVal, + &Symbol::new(&e, LP_TOKEN_VAL_KEY), LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED, ); e.storage() .persistent() - .get::(&BackstopDataKey::LPTknVal) + .get::(&Symbol::new(&e, LP_TOKEN_VAL_KEY)) .unwrap_optimized() } @@ -505,9 +533,9 @@ pub fn get_lp_token_val(e: &Env) -> (i128, i128) { pub fn set_lp_token_val(e: &Env, share_val: &(i128, i128)) { e.storage() .persistent() - .set::(&BackstopDataKey::LPTknVal, share_val); + .set::(&Symbol::new(&e, LP_TOKEN_VAL_KEY), share_val); e.storage().persistent().bump( - &BackstopDataKey::LPTknVal, + &Symbol::new(&e, LP_TOKEN_VAL_KEY), LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED, ); diff --git a/backstop/src/testutils.rs b/backstop/src/testutils.rs index 3f7041dd..bcb945c4 100644 --- a/backstop/src/testutils.rs +++ b/backstop/src/testutils.rs @@ -8,11 +8,14 @@ use crate::{ }; use soroban_sdk::{ - testutils::Address as _, unwrap::UnwrapOptimized, vec, Address, Env, IntoVal, Vec, + testutils::{Address as _, Ledger, LedgerInfo}, + unwrap::UnwrapOptimized, + vec, Address, Env, IntoVal, Vec, }; use sep_41_token::testutils::{MockTokenClient, MockTokenWASM}; +use emitter::{EmitterClient, EmitterContract}; use mock_pool_factory::{MockPoolFactory, MockPoolFactoryClient}; pub(crate) fn create_backstop(e: &Env) -> Address { @@ -80,6 +83,44 @@ pub(crate) fn create_mock_pool_factory<'a>( ) } +pub(crate) fn create_emitter<'a>( + e: &Env, + backstop: &Address, + backstop_token: &Address, + blnd_token: &Address, + emitter_last_distro: u64, +) -> (Address, EmitterClient<'a>) { + let contract_address = e.register_contract(None, EmitterContract {}); + + let prev_timestamp = e.ledger().timestamp(); + e.ledger().set(LedgerInfo { + timestamp: emitter_last_distro, + protocol_version: 20, + sequence_number: 0, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + e.as_contract(backstop, || { + storage::set_emitter(e, &contract_address); + }); + let client = EmitterClient::new(e, &contract_address); + client.initialize(&blnd_token, &backstop, &backstop_token); + e.ledger().set(LedgerInfo { + timestamp: prev_timestamp, + protocol_version: 20, + sequence_number: 0, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + (contract_address.clone(), client) +} + /// Deploy a test Comet LP pool of 80% BLND / 20% USDC and set it as the backstop token. /// /// Initializes the pool with the following settings: From d3bb588968febc92e7cad95eb3d48be2c5075fcd Mon Sep 17 00:00:00 2001 From: mootz12 Date: Fri, 1 Dec 2023 15:12:33 -0500 Subject: [PATCH 3/5] pool: feat: consume emissions from backstop instead of EPS --- pool/Cargo.toml | 1 + pool/src/contract.rs | 13 +- pool/src/emissions/distributor.rs | 36 ++- pool/src/emissions/manager.rs | 373 ++++++++++-------------------- pool/src/emissions/mod.rs | 2 +- pool/src/pool/config.rs | 13 +- pool/src/pool/mod.rs | 1 - pool/src/pool/status.rs | 86 +++---- pool/src/storage.rs | 90 +++---- pool/src/testutils.rs | 17 ++ 10 files changed, 257 insertions(+), 375 deletions(-) diff --git a/pool/Cargo.toml b/pool/Cargo.toml index b4ac5bcd..6f1f3012 100644 --- a/pool/Cargo.toml +++ b/pool/Cargo.toml @@ -28,3 +28,4 @@ backstop = { path = "../backstop", features = ["testutils"] } sep-40-oracle = { workspace = true, features = ["testutils"] } sep-41-token = { workspace = true, features = ["testutils"] } mock-pool-factory = { path = "../mocks/mock-pool-factory", features = ["testutils"] } +emitter = { path = "../emitter", features = ["testutils"] } diff --git a/pool/src/contract.rs b/pool/src/contract.rs index 72cb1101..808cc750 100644 --- a/pool/src/contract.rs +++ b/pool/src/contract.rs @@ -145,12 +145,11 @@ pub trait Pool { /********* Emission Functions **********/ - /// Update emissions for reserves for the next emission cycle + /// Consume emissions from the backstop and distribute to the reserves based + /// on the reserve emission configuration. /// - /// Needs to be performed each emission cycle, as determined by the expiration - /// - /// Returns the expiration timestamp - fn update_emissions(e: Env) -> u64; + /// Returns amount of new tokens emitted + fn gulp_emissions(e: Env) -> i128; /// (Admin only) Set the emission configuration for the pool /// @@ -324,9 +323,9 @@ impl Pool for PoolContract { /********* Emission Functions **********/ - fn update_emissions(e: Env) -> u64 { + fn gulp_emissions(e: Env) -> i128 { storage::bump_instance(&e); - let next_expiration = pool::update_pool_emissions(&e); + let next_expiration = emissions::gulp_emissions(&e); e.events() .publish((Symbol::new(&e, "update_emissions"),), next_expiration); diff --git a/pool/src/emissions/distributor.rs b/pool/src/emissions/distributor.rs index 83dd1628..0c43cf0c 100644 --- a/pool/src/emissions/distributor.rs +++ b/pool/src/emissions/distributor.rs @@ -7,6 +7,7 @@ use crate::{ errors::PoolError, pool::User, storage::{self, ReserveEmissionsData, UserEmissionData}, + ReserveEmissionsConfig, }; /// Performs a claim against the given "reserve_token_ids" for "from" @@ -117,28 +118,43 @@ pub fn update_emission_data( supply: i128, supply_scalar: i128, ) -> Option { - let token_emission_config = match storage::get_res_emis_config(e, &res_token_id) { - Some(res) => res, + match storage::get_res_emis_config(e, &res_token_id) { + Some(emis_config) => Some(update_emission_data_with_config( + e, + res_token_id, + supply, + supply_scalar, + &emis_config, + )), None => return None, // no emission exist, no update is required - }; + } +} + +pub(super) fn update_emission_data_with_config( + e: &Env, + res_token_id: u32, + supply: i128, + supply_scalar: i128, + emis_config: &ReserveEmissionsConfig, +) -> ReserveEmissionsData { let token_emission_data = storage::get_res_emis_data(e, &res_token_id).unwrap_optimized(); // exists if config is written to - if token_emission_data.last_time >= token_emission_config.expiration + if token_emission_data.last_time >= emis_config.expiration || e.ledger().timestamp() == token_emission_data.last_time - || token_emission_config.eps == 0 + || emis_config.eps == 0 || supply == 0 { - return Some(token_emission_data); + return token_emission_data; } - let ledger_timestamp = if e.ledger().timestamp() > token_emission_config.expiration { - token_emission_config.expiration + let ledger_timestamp = if e.ledger().timestamp() > emis_config.expiration { + emis_config.expiration } else { e.ledger().timestamp() }; let additional_idx = (i128(ledger_timestamp - token_emission_data.last_time) - * i128(token_emission_config.eps)) + * i128(emis_config.eps)) .fixed_div_floor(supply, supply_scalar) .unwrap_optimized(); let new_data = ReserveEmissionsData { @@ -146,7 +162,7 @@ pub fn update_emission_data( last_time: ledger_timestamp, }; storage::set_res_emis_data(e, &res_token_id, &new_data); - Some(new_data) + new_data } fn update_user_emissions( diff --git a/pool/src/emissions/manager.rs b/pool/src/emissions/manager.rs index 26f398ff..98a8e7d7 100644 --- a/pool/src/emissions/manager.rs +++ b/pool/src/emissions/manager.rs @@ -1,7 +1,10 @@ use crate::{ + constants::SCALAR_7, + dependencies::BackstopClient, errors::PoolError, storage::{self, ReserveEmissionsConfig, ReserveEmissionsData}, }; +use cast::{i128, u64}; use fixed_point_math::FixedPoint; use soroban_sdk::{ contracttype, map, panic_with_error, unwrap::UnwrapOptimized, Address, Env, Map, Symbol, Vec, @@ -50,36 +53,45 @@ pub fn set_pool_emissions(e: &Env, res_emission_metadata: Vec u64 { - let cur_exp = storage::get_pool_emissions_expiration(e); - if next_exp <= cur_exp { - panic_with_error!(e, PoolError::BadRequest); - } +pub fn gulp_emissions(e: &Env) -> i128 { + let backstop = storage::get_backstop(e); + let new_emissions = + BackstopClient::new(e, &backstop).gulp_pool_emissions(&e.current_contract_address()); + do_gulp_emissions(e, new_emissions); + new_emissions +} +fn do_gulp_emissions(e: &Env, new_emissions: i128) { + // ensure enough tokens are being emitted to avoid rounding issues + if new_emissions < SCALAR_7 { + panic_with_error!(e, PoolError::BadRequest) + } let pool_emissions = storage::get_pool_emissions(e); let reserve_list = storage::get_res_list(e); for (res_token_id, res_eps_share) in pool_emissions.iter() { let reserve_index = res_token_id / 2; let res_asset_address = reserve_list.get_unchecked(reserve_index); - // update emissions data first to use the previous config until the current ledger timestamp - update_reserve_emission_data(e, &res_asset_address, res_token_id); - update_reserve_emission_config(e, res_token_id, next_exp, pool_eps, res_eps_share); + let new_reserve_emissions = i128(res_eps_share) + .fixed_mul_floor(new_emissions, SCALAR_7) + .unwrap_optimized(); + update_reserve_emission_config(e, &res_asset_address, res_token_id, new_reserve_emissions); } - - storage::set_pool_emissions_expiration(e, &next_exp); - next_exp } -fn update_reserve_emission_data(e: &Env, asset: &Address, res_token_id: u32) { - if storage::has_res_emis_data(e, &res_token_id) { +fn update_reserve_emission_config( + e: &Env, + asset: &Address, + res_token_id: u32, + new_reserve_emissions: i128, +) { + let mut tokens_left_to_emit = new_reserve_emissions; + if let Some(emis_config) = storage::get_res_emis_config(e, &res_token_id) { // data exists - update it with old config let reserve_config = storage::get_res_config(e, asset); let reserve_data = storage::get_res_data(e, asset); @@ -88,20 +100,26 @@ fn update_reserve_emission_data(e: &Env, asset: &Address, res_token_id: u32) { 1 => reserve_data.b_supply, _ => panic_with_error!(e, PoolError::BadRequest), }; - let mut emission_data = distributor::update_emission_data( + let mut emission_data = distributor::update_emission_data_with_config( e, res_token_id, supply, 10i128.pow(reserve_config.decimals), - ) - .unwrap(); // will always return a result + &emis_config, + ); if emission_data.last_time != e.ledger().timestamp() { // force the emission data to be updated to the current timestamp emission_data.last_time = e.ledger().timestamp(); storage::set_res_emis_data(e, &res_token_id, &emission_data); } + // determine the amount of tokens not emitted from the last config + if emis_config.expiration > e.ledger().timestamp() { + let time_since_last_emission = emis_config.expiration - e.ledger().timestamp(); + let tokens_since_last_emission = i128(emis_config.eps * time_since_last_emission); + tokens_left_to_emit += tokens_since_last_emission; + } } else { - // no data exists yet - first time this reserve token will get emission + // no config or data exists yet - first time this reserve token will get emission storage::set_res_emis_data( e, &res_token_id, @@ -111,27 +129,14 @@ fn update_reserve_emission_data(e: &Env, asset: &Address, res_token_id: u32) { }, ); } -} - -fn update_reserve_emission_config( - e: &Env, - res_token_id: u32, - expiration: u64, - pool_eps: u64, - eps_share: u64, -) { - let new_res_eps = eps_share - .fixed_mul_floor(pool_eps, 1_0000000) - .unwrap_optimized(); - let new_reserve_emis_config = ReserveEmissionsConfig { - expiration, - eps: new_res_eps, - }; - + let expiration = e.ledger().timestamp() + 7 * 24 * 60 * 60; + let eps = u64(tokens_left_to_emit / (7 * 24 * 60 * 60)).unwrap_optimized(); + let new_reserve_emis_config = ReserveEmissionsConfig { expiration, eps }; storage::set_res_emis_config(e, &res_token_id, &new_reserve_emis_config); + e.events().publish( - (Symbol::new(e, "e_config"),), - (res_token_id, new_res_eps, expiration), + (Symbol::new(e, "reserve_emission_update"),), + (res_token_id, eps, expiration), ) } @@ -146,10 +151,10 @@ mod tests { vec, Address, }; - /********** update emissions cycle ********/ + /********** gulp_emissions ********/ #[test] - fn test_update_emissions_cycle_no_emitted_reserves_does_nothing() { + fn test_gulp_emissions_no_pool_emissions_does_nothing() { let e = Env::default(); e.mock_all_auths(); e.ledger().set(LedgerInfo { @@ -166,8 +171,7 @@ mod tests { let pool = testutils::create_pool(&e); let bombadil = Address::random(&e); - let next_exp = 1500604800; - let pool_eps = 0_5000000; + let new_emissions: i128 = 302_400_0000000; let pool_emissions: Map = map![&e]; let (reserve_config, reserve_data) = testutils::default_reserve_meta(); @@ -179,9 +183,7 @@ mod tests { e.as_contract(&pool, || { storage::set_pool_emissions(&e, &pool_emissions); - update_emissions_cycle(&e, next_exp, pool_eps); - - assert_eq!(storage::get_pool_emissions_expiration(&e), next_exp); + do_gulp_emissions(&e, new_emissions); assert!(storage::get_res_emis_config(&e, &0).is_none()); assert!(storage::get_res_emis_config(&e, &1).is_none()); @@ -191,7 +193,7 @@ mod tests { } #[test] - fn test_update_emissions_cycle_sets_reserve_emission_when_emitting_both() { + fn test_gulp_emissions() { let e = Env::default(); e.mock_all_auths(); e.ledger().set(LedgerInfo { @@ -208,11 +210,11 @@ mod tests { let pool = testutils::create_pool(&e); let bombadil = Address::random(&e); - let next_exp = 1500604800; - let pool_eps = 0_5000000; + let new_emissions: i128 = 302_400_0000000; let pool_emissions: Map = map![ &e, - (2, 0_7500000), // reserve_1 liability + (0, 0_2000000), // reserve_0 liability + (2, 0_5500000), // reserve_1 liability (3, 0_2500000) // reserve_1 supply ]; @@ -225,215 +227,69 @@ mod tests { let (underlying_2, _) = testutils::create_token_contract(&e, &bombadil); testutils::create_reserve(&e, &pool, &underlying_2, &reserve_config, &reserve_data); - e.as_contract(&pool, || { - storage::set_pool_emissions(&e, &pool_emissions); - - update_emissions_cycle(&e, next_exp, pool_eps); - - assert_eq!(storage::get_pool_emissions_expiration(&e), next_exp); - - assert!(storage::get_res_emis_config(&e, &0).is_none()); - assert!(storage::get_res_emis_config(&e, &1).is_none()); - assert!(storage::get_res_emis_config(&e, &4).is_none()); - assert!(storage::get_res_emis_config(&e, &5).is_none()); - - let r_1_l_config = storage::get_res_emis_config(&e, &2).unwrap_optimized(); - let r_1_s_config = storage::get_res_emis_config(&e, &3).unwrap_optimized(); - assert_eq!(r_1_l_config.expiration, next_exp); - assert_eq!(r_1_l_config.eps, 0_3750000); - assert_eq!(r_1_s_config.expiration, next_exp); - assert_eq!(r_1_s_config.eps, 0_1250000); - - // verify empty data was created for both - let r_1_l_data = storage::get_res_emis_data(&e, &2).unwrap_optimized(); - let r_1_s_data = storage::get_res_emis_data(&e, &3).unwrap_optimized(); - assert_eq!(r_1_l_data.index, 0); - assert_eq!(r_1_l_data.last_time, 1500000000); - assert_eq!(r_1_s_data.index, 0); - assert_eq!(r_1_s_data.last_time, 1500000000); - }); - } - - #[test] - fn test_update_emissions_cycle_sets_reserve_emission_config_and_data() { - let e = Env::default(); - e.mock_all_auths(); - - let pool = testutils::create_pool(&e); - let bombadil = Address::random(&e); - - e.ledger().set(LedgerInfo { - timestamp: 1500000000, - protocol_version: 20, - sequence_number: 20100, - network_id: Default::default(), - base_reserve: 10, - min_temp_entry_expiration: 10, - min_persistent_entry_expiration: 10, - max_entry_expiration: 2000000, - }); - - let next_exp = 1500604800; - let pool_eps = 0_5000000; - let pool_emissions: Map = map![ - &e, - (0, 0_2500000), // reserve_0 liabilities - (5, 0_7500000) // reserve_1 supply - ]; - - let old_r_l_0_config = ReserveEmissionsConfig { - eps: 0_2000000, - expiration: 1500000100, + // setup reserve_0 liability to have emissions remaining + let old_r_0_l_config = ReserveEmissionsConfig { + eps: 0_1500000, + expiration: 1500000200, }; - let old_r_l_0_data = ReserveEmissionsData { - index: 100, - last_time: 1499980000, - }; - let old_r_s_2_config = ReserveEmissionsConfig { - eps: 0_3000000, - expiration: 1500000100, - }; - let old_r_s_2_data = ReserveEmissionsData { - index: 500, + let old_r_0_l_data = ReserveEmissionsData { + index: 99999, last_time: 1499980000, }; - let (reserve_config, mut reserve_data) = testutils::default_reserve_meta(); - reserve_data.last_time = 1499900000; - let (underlying_0, _) = testutils::create_token_contract(&e, &bombadil); - testutils::create_reserve(&e, &pool, &underlying_0, &reserve_config, &reserve_data); - let (underlying_1, _) = testutils::create_token_contract(&e, &bombadil); - testutils::create_reserve(&e, &pool, &underlying_1, &reserve_config, &reserve_data); - let (underlying_2, _) = testutils::create_token_contract(&e, &bombadil); - reserve_data.b_supply = 100_0000000; - reserve_data.d_supply = 50_0000000; - testutils::create_reserve(&e, &pool, &underlying_2, &reserve_config, &reserve_data); + // setup reserve_1 liability to have no emissions + // steup reserve_1 supply to have emissions expired + let old_r_1_s_config = ReserveEmissionsConfig { + eps: 0_3500000, + expiration: 1499990000, + }; + let old_r_1_s_data = ReserveEmissionsData { + index: 11111, + last_time: 1499990000, + }; e.as_contract(&pool, || { storage::set_pool_emissions(&e, &pool_emissions); - storage::set_res_emis_config(&e, &0, &old_r_l_0_config); - storage::set_res_emis_data(&e, &0, &old_r_l_0_data); - storage::set_res_emis_config(&e, &5, &old_r_s_2_config); - storage::set_res_emis_data(&e, &5, &old_r_s_2_data); + storage::set_res_emis_config(&e, &0, &old_r_0_l_config); + storage::set_res_emis_data(&e, &0, &old_r_0_l_data); + storage::set_res_emis_config(&e, &3, &old_r_1_s_config); + storage::set_res_emis_data(&e, &3, &old_r_1_s_data); - let result = update_emissions_cycle(&e, next_exp, pool_eps); - - assert_eq!(storage::get_pool_emissions_expiration(&e), next_exp); - assert_eq!(result, next_exp); + do_gulp_emissions(&e, new_emissions); assert!(storage::get_res_emis_config(&e, &1).is_none()); - assert!(storage::get_res_emis_config(&e, &2).is_none()); - assert!(storage::get_res_emis_config(&e, &3).is_none()); assert!(storage::get_res_emis_config(&e, &4).is_none()); + assert!(storage::get_res_emis_config(&e, &5).is_none()); + // verify reserve_0 liability leftover emissions were carried over let r_0_l_config = storage::get_res_emis_config(&e, &0).unwrap_optimized(); - let r_2_s_config = storage::get_res_emis_config(&e, &5).unwrap_optimized(); - assert_eq!(r_0_l_config.expiration, next_exp); - assert_eq!(r_0_l_config.eps, 0_1250000); - assert_eq!(r_2_s_config.expiration, next_exp); - assert_eq!(r_2_s_config.eps, 0_3750000); - let r_0_l_data = storage::get_res_emis_data(&e, &0).unwrap_optimized(); - let r_2_s_data = storage::get_res_emis_data(&e, &5).unwrap_optimized(); - assert_eq!(r_0_l_data.index, 533333433); + assert_eq!(r_0_l_config.expiration, 1500000000 + 7 * 24 * 60 * 60); + assert_eq!(r_0_l_config.eps, 0_1000496); + assert_eq!(r_0_l_data.index, 99999 + 40 * SCALAR_7); assert_eq!(r_0_l_data.last_time, 1500000000); - assert_eq!(r_2_s_data.index, 600000500); - assert_eq!(r_2_s_data.last_time, 1500000000); - }); - } - - #[test] - fn test_update_emissions_cycle_all_data_set_to_ledger_timestamp() { - let e = Env::default(); - e.mock_all_auths(); - - let pool = testutils::create_pool(&e); - let bombadil = Address::random(&e); - - e.ledger().set(LedgerInfo { - timestamp: 1500100000, - protocol_version: 20, - sequence_number: 20100, - network_id: Default::default(), - base_reserve: 10, - min_temp_entry_expiration: 10, - min_persistent_entry_expiration: 10, - max_entry_expiration: 2000000, - }); - - let next_exp = 1500704800; - let pool_eps = 0_5000000; - let pool_emissions: Map = map![ - &e, - (0, 0_2500000), // reserve_0 liabilities - (5, 0_7500000) // reserve_1 supply - ]; - - let old_r_l_0_config = ReserveEmissionsConfig { - eps: 0_2000000, - expiration: 1500000100, - }; - let old_r_l_0_data = ReserveEmissionsData { - index: 100, - last_time: 1500000200, - }; - let old_r_s_2_config = ReserveEmissionsConfig { - eps: 0_3000000, - expiration: 1500000100, - }; - let old_r_s_2_data = ReserveEmissionsData { - index: 500, - last_time: 1500000100, - }; - - let (reserve_config, mut reserve_data) = testutils::default_reserve_meta(); - reserve_data.last_time = 1499900000; - let (underlying_0, _) = testutils::create_token_contract(&e, &bombadil); - testutils::create_reserve(&e, &pool, &underlying_0, &reserve_config, &reserve_data); - let (underlying_1, _) = testutils::create_token_contract(&e, &bombadil); - testutils::create_reserve(&e, &pool, &underlying_1, &reserve_config, &reserve_data); - let (underlying_2, _) = testutils::create_token_contract(&e, &bombadil); - reserve_data.b_supply = 100_0000000; - reserve_data.d_supply = 50_0000000; - testutils::create_reserve(&e, &pool, &underlying_2, &reserve_config, &reserve_data); - - e.as_contract(&pool, || { - storage::set_pool_emissions(&e, &pool_emissions); - storage::set_res_emis_config(&e, &0, &old_r_l_0_config); - storage::set_res_emis_data(&e, &0, &old_r_l_0_data); - storage::set_res_emis_config(&e, &5, &old_r_s_2_config); - storage::set_res_emis_data(&e, &5, &old_r_s_2_data); - let result = update_emissions_cycle(&e, next_exp, pool_eps); - - assert_eq!(storage::get_pool_emissions_expiration(&e), next_exp); - assert_eq!(result, next_exp); - - assert!(storage::get_res_emis_config(&e, &1).is_none()); - assert!(storage::get_res_emis_config(&e, &2).is_none()); - assert!(storage::get_res_emis_config(&e, &3).is_none()); - assert!(storage::get_res_emis_config(&e, &4).is_none()); - - let r_0_l_config = storage::get_res_emis_config(&e, &0).unwrap_optimized(); - let r_2_s_config = storage::get_res_emis_config(&e, &5).unwrap_optimized(); - assert_eq!(r_0_l_config.expiration, next_exp); - assert_eq!(r_0_l_config.eps, 0_1250000); - assert_eq!(r_2_s_config.expiration, next_exp); - assert_eq!(r_2_s_config.eps, 0_3750000); + // verify reserve_1 liability initialized emissions + let r_1_l_config = storage::get_res_emis_config(&e, &2).unwrap_optimized(); + let r_1_l_data = storage::get_res_emis_data(&e, &2).unwrap_optimized(); + assert_eq!(r_1_l_config.expiration, 1500000000 + 7 * 24 * 60 * 60); + assert_eq!(r_1_l_config.eps, 0_2750000); + assert_eq!(r_1_l_data.index, 0); + assert_eq!(r_1_l_data.last_time, 1500000000); - // should not accrue any value to index due to already passing the last expiration - let r_0_l_data = storage::get_res_emis_data(&e, &0).unwrap_optimized(); - let r_2_s_data = storage::get_res_emis_data(&e, &5).unwrap_optimized(); - assert_eq!(r_0_l_data.index, 100); - assert_eq!(r_0_l_data.last_time, 1500100000); - assert_eq!(r_2_s_data.index, 500); - assert_eq!(r_2_s_data.last_time, 1500100000); + // verify reserve_1 supply updated reserve data to the correct timestamp + let r_1_s_config = storage::get_res_emis_config(&e, &3).unwrap_optimized(); + let r_1_s_data = storage::get_res_emis_data(&e, &3).unwrap_optimized(); + assert_eq!(r_1_s_config.expiration, 1500000000 + 7 * 24 * 60 * 60); + assert_eq!(r_1_s_config.eps, 0_1250000); + assert_eq!(r_1_s_data.index, 11111); + assert_eq!(r_1_s_data.last_time, 1500000000); }); } #[test] #[should_panic(expected = "Error(Contract, #2)")] - fn test_update_emissions_cycle_panics_if_already_updated() { + fn test_gulp_emissions_too_small() { let e = Env::default(); e.mock_all_auths(); e.ledger().set(LedgerInfo { @@ -450,9 +306,13 @@ mod tests { let pool = testutils::create_pool(&e); let bombadil = Address::random(&e); - let next_exp = 1500604800; - let pool_eps = 0_5000000; - let pool_emissions: Map = map![&e, (2, 0_7500000), (3, 0_2500000)]; + let new_emissions: i128 = 1000000; + let pool_emissions: Map = map![ + &e, + (0, 0_2000000), // reserve_0 liability + (2, 0_5500000), // reserve_1 liability + (3, 0_2500000) // reserve_1 supply + ]; let (reserve_config, mut reserve_data) = testutils::default_reserve_meta(); reserve_data.last_time = 1499900000; @@ -461,15 +321,37 @@ mod tests { let (underlying_1, _) = testutils::create_token_contract(&e, &bombadil); testutils::create_reserve(&e, &pool, &underlying_1, &reserve_config, &reserve_data); let (underlying_2, _) = testutils::create_token_contract(&e, &bombadil); - reserve_data.b_supply = 100_0000000; - reserve_data.d_supply = 50_0000000; testutils::create_reserve(&e, &pool, &underlying_2, &reserve_config, &reserve_data); + // setup reserve_0 liability to have emissions remaining + let old_r_0_l_config = ReserveEmissionsConfig { + eps: 0_1500000, + expiration: 1500000200, + }; + let old_r_0_l_data = ReserveEmissionsData { + index: 99999, + last_time: 1499980000, + }; + + // setup reserve_1 liability to have no emissions + + // steup reserve_1 supply to have emissions expired + let old_r_1_s_config = ReserveEmissionsConfig { + eps: 0_3500000, + expiration: 1499990000, + }; + let old_r_1_s_data = ReserveEmissionsData { + index: 11111, + last_time: 1499990000, + }; e.as_contract(&pool, || { - storage::set_pool_emissions_expiration(&e, &1500604800); storage::set_pool_emissions(&e, &pool_emissions); + storage::set_res_emis_config(&e, &0, &old_r_0_l_config); + storage::set_res_emis_data(&e, &0, &old_r_0_l_data); + storage::set_res_emis_config(&e, &3, &old_r_1_s_config); + storage::set_res_emis_data(&e, &3, &old_r_1_s_data); - update_emissions_cycle(&e, next_exp, pool_eps); + do_gulp_emissions(&e, new_emissions); }); } @@ -575,7 +457,6 @@ mod tests { ]; e.as_contract(&pool, || { - storage::set_pool_emissions_expiration(&e, &1000); storage::set_pool_emissions(&e, &pool_emissions); set_pool_emissions(&e, res_emission_metadata); diff --git a/pool/src/emissions/mod.rs b/pool/src/emissions/mod.rs index 2bf1e14c..2f937449 100644 --- a/pool/src/emissions/mod.rs +++ b/pool/src/emissions/mod.rs @@ -1,5 +1,5 @@ mod manager; -pub use manager::{set_pool_emissions, update_emissions_cycle, ReserveEmissionMetadata}; +pub use manager::{gulp_emissions, set_pool_emissions, ReserveEmissionMetadata}; mod distributor; pub use distributor::{execute_claim, update_emissions}; diff --git a/pool/src/pool/config.rs b/pool/src/pool/config.rs index 4d9d8e4c..c986103a 100644 --- a/pool/src/pool/config.rs +++ b/pool/src/pool/config.rs @@ -1,11 +1,8 @@ use crate::{ - dependencies::BackstopClient, - emissions, errors::PoolError, storage::{self, PoolConfig, ReserveConfig, ReserveData}, }; -use cast::u64; -use soroban_sdk::{panic_with_error, unwrap::UnwrapOptimized, Address, Env, Symbol}; +use soroban_sdk::{panic_with_error, Address, Env, Symbol}; use super::pool::Pool; @@ -113,14 +110,6 @@ pub fn execute_update_reserve(e: &Env, asset: &Address, config: &ReserveConfig) storage::set_res_config(e, asset, &new_config); } -// Update the pool emission information from the backstop -pub fn update_pool_emissions(e: &Env) -> u64 { - let backstop_address = storage::get_backstop(e); - let backstop_client = BackstopClient::new(e, &backstop_address); - let (pool_eps, next_exp) = backstop_client.pool_eps(&e.current_contract_address()); - emissions::update_emissions_cycle(e, next_exp, u64(pool_eps).unwrap_optimized()) -} - #[allow(clippy::zero_prefixed_literal)] fn require_valid_reserve_metadata(e: &Env, metadata: &ReserveConfig) { if metadata.decimals > 18 diff --git a/pool/src/pool/mod.rs b/pool/src/pool/mod.rs index 44f8c3e6..5f7ef778 100644 --- a/pool/src/pool/mod.rs +++ b/pool/src/pool/mod.rs @@ -7,7 +7,6 @@ pub use bad_debt::{burn_backstop_bad_debt, transfer_bad_debt_to_backstop}; mod config; pub use config::{ execute_initialize, execute_update_pool, execute_update_reserve, initialize_reserve, - update_pool_emissions, }; mod health_factor; diff --git a/pool/src/pool/status.rs b/pool/src/pool/status.rs index 5cf14068..e08432f1 100644 --- a/pool/src/pool/status.rs +++ b/pool/src/pool/status.rs @@ -143,49 +143,49 @@ mod tests { }); } - // #[test] - // #[should_panic(expected = "Error(Contract, #11)")] - // fn test_set_pool_status_blocks_without_backstop_minimum() { - // let e = Env::default(); - // e.budget().reset_unlimited(); - // e.mock_all_auths_allowing_non_root_auth(); - // let pool_id = create_pool(&e); - // let oracle_id = Address::random(&e); - - // let bombadil = Address::random(&e); - // let samwise = Address::random(&e); - - // let (blnd, blnd_client) = create_token_contract(&e, &bombadil); - // let (usdc, usdc_client) = create_token_contract(&e, &bombadil); - // let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); - // let (backstop_id, backstop_client) = create_backstop(&e); - // setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); - - // // mint lp tokens - under limit - // blnd_client.mint(&samwise, &400_001_0000000); - // blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); - // usdc_client.mint(&samwise, &10_001_0000000); - // usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); - // lp_token_client.join_pool( - // &40_000_0000000, - // &vec![&e, 400_001_0000000, 10_001_0000000], - // &samwise, - // ); - // backstop_client.deposit(&samwise, &pool_id, &40_000_0000000); - // backstop_client.update_tkn_val(); - - // let pool_config = PoolConfig { - // oracle: oracle_id, - // bstop_rate: 0, - // status: 1, - // }; - // e.as_contract(&pool_id, || { - // storage::set_admin(&e, &bombadil); - // storage::set_pool_config(&e, &pool_config); - - // set_pool_status(&e, 0); - // }); - // } + #[test] + #[should_panic(expected = "Error(Contract, #11)")] + fn test_set_pool_status_blocks_without_backstop_minimum() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::random(&e); + + let bombadil = Address::random(&e); + let samwise = Address::random(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens - under limit + blnd_client.mint(&samwise, &400_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &10_001_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &40_000_0000000, + &vec![&e, 400_001_0000000, 10_001_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &40_000_0000000); + backstop_client.update_tkn_val(); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 1, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + set_pool_status(&e, 0); + }); + } #[test] fn test_update_pool_status_active() { diff --git a/pool/src/storage.rs b/pool/src/storage.rs index aeb740fc..b882a679 100644 --- a/pool/src/storage.rs +++ b/pool/src/storage.rs @@ -87,6 +87,15 @@ pub struct UserEmissionData { /********** Storage Key Types **********/ +const ADMIN_KEY: &str = "Admin"; +const NAME_KEY: &str = "Name"; +const BACKSTOP_KEY: &str = "Backstop"; +const BLND_TOKEN_KEY: &str = "BLNDTkn"; +const USDC_TOKEN_KEY: &str = "USDCTkn"; +const POOL_CONFIG_KEY: &str = "Config"; +const RES_LIST_KEY: &str = "ResList"; +const POOL_EMIS_KEY: &str = "PoolEmis"; + #[derive(Clone)] #[contracttype] pub struct UserReserveKey { @@ -191,7 +200,7 @@ pub fn set_user_positions(e: &Env, user: &Address, positions: &Positions) { pub fn get_admin(e: &Env) -> Address { e.storage() .instance() - .get(&Symbol::new(e, "Admin")) + .get(&Symbol::new(e, ADMIN_KEY)) .unwrap_optimized() } @@ -202,12 +211,12 @@ pub fn get_admin(e: &Env) -> Address { pub fn set_admin(e: &Env, new_admin: &Address) { e.storage() .instance() - .set::(&Symbol::new(e, "Admin"), new_admin); + .set::(&Symbol::new(e, ADMIN_KEY), new_admin); } /// Checks if an admin is set pub fn has_admin(e: &Env) -> bool { - e.storage().instance().has(&Symbol::new(e, "Admin")) + e.storage().instance().has(&Symbol::new(e, ADMIN_KEY)) } /********** Metadata **********/ @@ -219,7 +228,7 @@ pub fn has_admin(e: &Env) -> bool { pub fn set_name(e: &Env, name: &Symbol) { e.storage() .instance() - .set::(&Symbol::new(e, "Name"), name); + .set::(&Symbol::new(e, NAME_KEY), name); } /********** Backstop **********/ @@ -231,7 +240,7 @@ pub fn set_name(e: &Env, name: &Symbol) { pub fn get_backstop(e: &Env) -> Address { e.storage() .instance() - .get(&Symbol::new(e, "Backstop")) + .get(&Symbol::new(e, BACKSTOP_KEY)) .unwrap_optimized() } @@ -242,7 +251,7 @@ pub fn get_backstop(e: &Env) -> Address { pub fn set_backstop(e: &Env, backstop: &Address) { e.storage() .instance() - .set::(&Symbol::new(e, "Backstop"), backstop); + .set::(&Symbol::new(e, BACKSTOP_KEY), backstop); } /********** External Token Contracts **********/ @@ -251,7 +260,7 @@ pub fn set_backstop(e: &Env, backstop: &Address) { pub fn get_blnd_token(e: &Env) -> Address { e.storage() .instance() - .get(&Symbol::new(e, "BLNDTkn")) + .get(&Symbol::new(e, BLND_TOKEN_KEY)) .unwrap_optimized() } @@ -262,14 +271,14 @@ pub fn get_blnd_token(e: &Env) -> Address { pub fn set_blnd_token(e: &Env, blnd_token_id: &Address) { e.storage() .instance() - .set::(&Symbol::new(e, "BLNDTkn"), blnd_token_id); + .set::(&Symbol::new(e, BLND_TOKEN_KEY), blnd_token_id); } /// Fetch the USDC token ID pub fn get_usdc_token(e: &Env) -> Address { e.storage() .instance() - .get(&Symbol::new(e, "USDCTkn")) + .get(&Symbol::new(e, USDC_TOKEN_KEY)) .unwrap_optimized() } @@ -280,7 +289,7 @@ pub fn get_usdc_token(e: &Env) -> Address { pub fn set_usdc_token(e: &Env, usdc_token_id: &Address) { e.storage() .instance() - .set::(&Symbol::new(e, "USDCTkn"), usdc_token_id); + .set::(&Symbol::new(e, USDC_TOKEN_KEY), usdc_token_id); } /********** Pool Config **********/ @@ -292,7 +301,7 @@ pub fn set_usdc_token(e: &Env, usdc_token_id: &Address) { pub fn get_pool_config(e: &Env) -> PoolConfig { e.storage() .instance() - .get(&Symbol::new(e, "PoolConfig")) + .get(&Symbol::new(e, POOL_CONFIG_KEY)) .unwrap_optimized() } @@ -303,7 +312,7 @@ pub fn get_pool_config(e: &Env) -> PoolConfig { pub fn set_pool_config(e: &Env, config: &PoolConfig) { e.storage() .instance() - .set::(&Symbol::new(e, "PoolConfig"), config); + .set::(&Symbol::new(e, POOL_CONFIG_KEY), config); } /********** Reserve Config (ResConfig) **********/ @@ -389,10 +398,9 @@ pub fn set_res_data(e: &Env, asset: &Address, data: &ReserveData) { /// Fetch the list of reserves pub fn get_res_list(e: &Env) -> Vec
{ - let key = Symbol::new(e, "ResList"); get_persistent_default( e, - &key, + &Symbol::new(e, RES_LIST_KEY), vec![e], LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED, @@ -415,13 +423,14 @@ pub fn push_res_list(e: &Env, asset: &Address) -> u32 { } res_list.push_back(asset.clone()); let new_index = res_list.len() - 1; - let key = Symbol::new(e, "ResList"); - e.storage() - .persistent() - .set::>(&key, &res_list); e.storage() .persistent() - .bump(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); + .set::>(&Symbol::new(e, RES_LIST_KEY), &res_list); + e.storage().persistent().bump( + &Symbol::new(e, RES_LIST_KEY), + LEDGER_THRESHOLD_SHARED, + LEDGER_BUMP_SHARED, + ); new_index } @@ -476,15 +485,6 @@ pub fn get_res_emis_data(e: &Env, res_token_index: &u32) -> Option bool { - let key = PoolDataKey::EmisData(*res_token_index); - e.storage().persistent().has(&key) -} - /// Set the emission data for the reserve b or d token /// /// ### Arguments @@ -545,10 +545,9 @@ pub fn set_user_emissions(e: &Env, user: &Address, res_token_index: &u32, data: /// Fetch the pool reserve emissions pub fn get_pool_emissions(e: &Env) -> Map { - let key = Symbol::new(e, "PoolEmis"); get_persistent_default::>( e, - &key, + &Symbol::new(e, POOL_EMIS_KEY), map![e], LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED, @@ -560,33 +559,14 @@ pub fn get_pool_emissions(e: &Env) -> Map { /// ### Arguments /// * `emissions` - The map of emissions by reserve token id to EPS pub fn set_pool_emissions(e: &Env, emissions: &Map) { - let key = Symbol::new(e, "PoolEmis"); e.storage() .persistent() - .set::>(&key, emissions); - e.storage() - .persistent() - .bump(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); -} - -/// Fetch the pool emission expiration timestamps -pub fn get_pool_emissions_expiration(e: &Env) -> u64 { - let key = Symbol::new(e, "EmisExp"); - get_persistent_default(e, &key, 0u64, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED) -} - -/// Set the pool emission configuration -/// -/// ### Arguments -/// * `expiration` - The pool's emission configuration -pub fn set_pool_emissions_expiration(e: &Env, expiration: &u64) { - let key = Symbol::new(e, "EmisExp"); - e.storage() - .persistent() - .set::(&key, expiration); - e.storage() - .persistent() - .bump(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); + .set::>(&Symbol::new(e, POOL_EMIS_KEY), emissions); + e.storage().persistent().bump( + &Symbol::new(e, POOL_EMIS_KEY), + LEDGER_THRESHOLD_SHARED, + LEDGER_BUMP_SHARED, + ); } /********** Auctions ***********/ diff --git a/pool/src/testutils.rs b/pool/src/testutils.rs index 8d60d4f6..d40da87c 100644 --- a/pool/src/testutils.rs +++ b/pool/src/testutils.rs @@ -6,6 +6,7 @@ use crate::{ storage::{self, ReserveConfig, ReserveData}, PoolContract, }; +use emitter::{EmitterClient, EmitterContract}; use fixed_point_math::FixedPoint; use sep_40_oracle::testutils::{MockPriceOracleClient, MockPriceOracleWASM}; use sep_41_token::testutils::{MockTokenClient, MockTokenWASM}; @@ -83,6 +84,20 @@ pub(crate) fn create_mock_pool_factory(e: &Env) -> (Address, MockPoolFactoryClie ) } +//***** Pool Factory ****** + +pub(crate) fn create_emitter<'a>( + e: &Env, + backstop_id: &Address, + backstop_token: &Address, + blnd_token: &Address, +) -> (Address, EmitterClient<'a>) { + let contract_address = e.register_contract(None, EmitterContract {}); + let client = EmitterClient::new(e, &contract_address); + client.initialize(blnd_token, backstop_id, backstop_token); + (contract_address.clone(), client) +} + //***** Backstop ****** mod comet { @@ -107,8 +122,10 @@ pub(crate) fn setup_backstop( ) { let (pool_factory, mock_pool_factory_client) = create_mock_pool_factory(e); mock_pool_factory_client.set_pool(pool_address); + let (emitter, _) = create_emitter(e, backstop_id, backstop_token, blnd_token); BackstopClient::new(e, backstop_id).initialize( backstop_token, + &emitter, usdc_token, blnd_token, &pool_factory, From 027c8c6869db1d72d97e58cb18b4e902ffd26f61 Mon Sep 17 00:00:00 2001 From: mootz12 Date: Fri, 1 Dec 2023 16:31:50 -0500 Subject: [PATCH 4/5] test: fix integration tests for emission update --- emitter/src/contract.rs | 6 +- emitter/src/lib.rs | 1 + pool-factory/src/test.rs | 2 +- test-suites/src/setup.rs | 52 +++++++-------- test-suites/src/test_fixture.rs | 37 +++++------ test-suites/tests/test_backstop.rs | 7 +- test-suites/tests/test_emitter.rs | 81 +++++++++++++++++++++-- test-suites/tests/test_liquidation.rs | 4 +- test-suites/tests/test_pool.rs | 4 +- test-suites/tests/test_wasm_happy_path.rs | 37 ++++++----- 10 files changed, 147 insertions(+), 84 deletions(-) diff --git a/emitter/src/contract.rs b/emitter/src/contract.rs index b5957be4..c5c142e3 100644 --- a/emitter/src/contract.rs +++ b/emitter/src/contract.rs @@ -112,8 +112,7 @@ impl Emitter for EmitterContract { let swap = backstop_manager::execute_queue_swap_backstop(&e, &new_backstop, &new_backstop_token); - e.events() - .publish((Symbol::new(&e, "queue_swap_backstop"),), swap); + e.events().publish((Symbol::new(&e, "q_swap"),), swap); } fn get_queued_swap(e: Env) -> Option { @@ -124,8 +123,7 @@ impl Emitter for EmitterContract { storage::bump_instance(&e); let swap = backstop_manager::execute_cancel_swap_backstop(&e); - e.events() - .publish((Symbol::new(&e, "cancel_swap_backstop"),), swap); + e.events().publish((Symbol::new(&e, "del_swap"),), swap); } fn swap_backstop(e: Env) { diff --git a/emitter/src/lib.rs b/emitter/src/lib.rs index b93a081d..c35f5de6 100644 --- a/emitter/src/lib.rs +++ b/emitter/src/lib.rs @@ -11,6 +11,7 @@ mod errors; mod storage; mod testutils; +pub use backstop_manager::Swap; pub use contract::*; pub use errors::EmitterError; pub use storage::EmitterDataKey; diff --git a/pool-factory/src/test.rs b/pool-factory/src/test.rs index c79ae70c..c7d0746a 100644 --- a/pool-factory/src/test.rs +++ b/pool-factory/src/test.rs @@ -87,7 +87,7 @@ fn test_pool_factory() { assert_eq!( e.storage() .instance() - .get::<_, pool::PoolConfig>(&Symbol::new(&e, "PoolConfig")) + .get::<_, pool::PoolConfig>(&Symbol::new(&e, "Config")) .unwrap(), pool::PoolConfig { oracle: oracle, diff --git a/test-suites/src/setup.rs b/test-suites/src/setup.rs index 79d13121..2141266c 100644 --- a/test-suites/src/setup.rs +++ b/test-suites/src/setup.rs @@ -85,24 +85,24 @@ pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { // enable emissions fixture.emitter.distribute(); - fixture.backstop.update_emission_cycle(); - pool_fixture.pool.update_emissions(); + fixture.backstop.gulp_emissions(); + pool_fixture.pool.gulp_emissions(); fixture.jump(60); - fixture.tokens[TokenIndex::STABLE].approve( - &frodo, - &pool_fixture.pool.address, - &i128::MAX, - &50000, - ); - fixture.tokens[TokenIndex::WETH].approve( - &frodo, - &pool_fixture.pool.address, - &i128::MAX, - &50000, - ); - fixture.tokens[TokenIndex::XLM].approve(&frodo, &pool_fixture.pool.address, &i128::MAX, &50000); + // fixture.tokens[TokenIndex::STABLE].approve( + // &frodo, + // &pool_fixture.pool.address, + // &i128::MAX, + // &(fixture.env.ledger().sequence() + 100), + // ); + // fixture.tokens[TokenIndex::WETH].approve( + // &frodo, + // &pool_fixture.pool.address, + // &i128::MAX, + // &(fixture.env.ledger().sequence() + 100), + // ); + // fixture.tokens[TokenIndex::XLM].approve(&frodo, &pool_fixture.pool.address, &i128::MAX, &50000); // supply and borrow STABLE for 80% utilization (close to target) let requests: Vec = vec![ @@ -205,13 +205,6 @@ mod tests { ); // validate emissions are turned on - assert_eq!( - ( - 0_300_0000, - fixture.env.ledger().timestamp() - 60 * 61 + 7 * 24 * 60 * 60 - ), - fixture.backstop.pool_eps(&pool_fixture.pool.address) - ); let (emis_config, emis_data) = fixture.read_reserve_emissions(0, TokenIndex::STABLE, 0); assert_eq!( emis_data.last_time, @@ -219,6 +212,10 @@ mod tests { ); assert_eq!(emis_data.index, 0); assert_eq!(0_180_0000, emis_config.eps); + assert_eq!( + fixture.env.ledger().timestamp() + 7 * 24 * 60 * 60 - 60 * 61, + emis_config.expiration + ) } #[test] @@ -261,13 +258,6 @@ mod tests { ); // validate emissions are turned on - assert_eq!( - ( - 0_300_0000, - fixture.env.ledger().timestamp() - 60 * 61 + 7 * 24 * 60 * 60 - ), - fixture.backstop.pool_eps(&pool_fixture.pool.address) - ); let (emis_config, emis_data) = fixture.read_reserve_emissions(0, TokenIndex::STABLE, 0); assert_eq!( emis_data.last_time, @@ -275,5 +265,9 @@ mod tests { ); assert_eq!(emis_data.index, 0); assert_eq!(0_180_0000, emis_config.eps); + assert_eq!( + fixture.env.ledger().timestamp() + 7 * 24 * 60 * 60 - 60 * 61, + emis_config.expiration + ) } } diff --git a/test-suites/src/test_fixture.rs b/test-suites/src/test_fixture.rs index 9306362a..a3a6d17e 100644 --- a/test-suites/src/test_fixture.rs +++ b/test-suites/src/test_fixture.rs @@ -73,7 +73,7 @@ impl TestFixture<'_> { e.ledger().set(LedgerInfo { timestamp: 1441065600, // Sept 1st, 2015 (backstop epoch) protocol_version: 20, - sequence_number: 5000000, + sequence_number: 150, network_id: Default::default(), base_reserve: 10, min_temp_entry_expiration: 999999, @@ -93,14 +93,23 @@ impl TestFixture<'_> { let (emitter_id, emitter_client) = create_emitter(&e, wasm); let (pool_factory_id, _) = create_pool_factory(&e, wasm); + // deploy external contracts + let (lp, lp_client) = create_lp_pool(&e, &bombadil, &blnd_id, &usdc_id); + // initialize emitter blnd_client.mint(&bombadil, &(10_000_000 * SCALAR_7)); blnd_client.set_admin(&emitter_id); - emitter_client.initialize(&backstop_id, &blnd_id); + emitter_client.initialize(&blnd_id, &backstop_id, &lp); // initialize backstop - let (lp, lp_client) = create_lp_pool(&e, &bombadil, &blnd_id, &usdc_id); - backstop_client.initialize(&lp, &usdc_id, &blnd_id, &pool_factory_id, &Map::new(&e)); + backstop_client.initialize( + &lp, + &emitter_id, + &usdc_id, + &blnd_id, + &pool_factory_id, + &Map::new(&e), + ); // initialize pool factory let pool_hash = e.deployer().upload_contract_wasm(POOL_WASM); @@ -136,19 +145,7 @@ impl TestFixture<'_> { 1_0000000 // stable ]); - // pass 1 day - e.ledger().set(LedgerInfo { - timestamp: 1441152000, - protocol_version: 20, - sequence_number: 150, - network_id: Default::default(), - base_reserve: 10, - min_temp_entry_expiration: 999999, - min_persistent_entry_expiration: 999999, - max_entry_expiration: u32::MAX, - }); - - TestFixture { + let fixture = TestFixture { env: e, bombadil, users: vec![], @@ -165,7 +162,9 @@ impl TestFixture<'_> { xlm_client, stable_client, ], - } + }; + fixture.jump(7 * 24 * 60 * 60); + fixture } pub fn create_pool(&mut self, name: Symbol, backstop_take_rate: u64) { @@ -205,7 +204,7 @@ impl TestFixture<'_> { self.env .storage() .instance() - .get(&Symbol::new(&self.env, "PoolConfig")) + .get(&Symbol::new(&self.env, "Config")) .unwrap() }) } diff --git a/test-suites/tests/test_backstop.rs b/test-suites/tests/test_backstop.rs index 38c7404d..4f7f9df1 100644 --- a/test-suites/tests/test_backstop.rs +++ b/test-suites/tests/test_backstop.rs @@ -28,6 +28,7 @@ fn test_backstop() { &Address::random(&fixture.env), &Address::random(&fixture.env), &Address::random(&fixture.env), + &Address::random(&fixture.env), &Map::new(&fixture.env), ); assert!(result.is_err()); @@ -197,7 +198,7 @@ fn test_backstop() { // Start the next emission cycle fixture.emitter.distribute(); - fixture.backstop.update_emission_cycle(); + fixture.backstop.gulp_emissions(); assert_eq!(fixture.env.auths().len(), 0); // Sam queues 100% of position for withdrawal @@ -260,7 +261,7 @@ fn test_backstop() { // Start the next emission cycle and jump 7 days (13d23hr total emissions for sam) fixture.jump(60 * 60 * 24 * 7); fixture.emitter.distribute(); - fixture.backstop.update_emission_cycle(); + fixture.backstop.gulp_emissions(); // Sam dequeues half of the withdrawal let amount = 6_250 * SCALAR_7; // shares @@ -312,7 +313,7 @@ fn test_backstop() { // Start the next emission cycle and jump 7 days (20d23hr total emissions for sam) fixture.jump(60 * 60 * 24 * 7); fixture.emitter.distribute(); - fixture.backstop.update_emission_cycle(); + fixture.backstop.gulp_emissions(); // Backstop loses money let amount = 1_000 * SCALAR_7; diff --git a/test-suites/tests/test_emitter.rs b/test-suites/tests/test_emitter.rs index c6512848..6387c58c 100644 --- a/test-suites/tests/test_emitter.rs +++ b/test-suites/tests/test_emitter.rs @@ -1,5 +1,6 @@ #![cfg(test)] +use emitter::Swap; use soroban_sdk::{ testutils::{Address as _, Events}, vec, Address, IntoVal, Symbol, @@ -25,6 +26,7 @@ fn test_emitter() { let result = fixture.emitter.try_initialize( &Address::random(&fixture.env), &Address::random(&fixture.env), + &Address::random(&fixture.env), ); assert!(result.is_err()); assert_eq!( @@ -65,19 +67,80 @@ fn test_emitter() { ] ); - // Mint enough tokens to a new backstop address to perform a swap, then swap the backstops + // Mint enough tokens to a new backstop address to perform a swap, then queue the swap let old_backstop_balance = bstop_token.balance(&fixture.backstop.address); let new_backstop = Address::random(&fixture.env); - fixture.tokens[TokenIndex::BLND].mint(&new_backstop, &(505_001 * SCALAR_7)); - fixture.tokens[TokenIndex::USDC].mint(&new_backstop, &(13_501 * SCALAR_7)); + fixture.tokens[TokenIndex::BLND].mint(&new_backstop, &(600_001 * SCALAR_7)); + fixture.tokens[TokenIndex::USDC].mint(&new_backstop, &(20_501 * SCALAR_7)); fixture.lp.join_pool( &(old_backstop_balance + 1), &vec![&fixture.env, 505_001 * SCALAR_7, 13_501 * SCALAR_7], &new_backstop, ); - fixture.emitter.swap_backstop(&new_backstop); + fixture + .emitter + .queue_swap_backstop(&new_backstop, &fixture.lp.address); + let swap_unlock_time = fixture.env.ledger().timestamp() + 31 * 24 * 60 * 60; assert_eq!(fixture.env.auths().len(), 0); - assert_eq!(fixture.emitter.get_backstop(), new_backstop.clone()); + assert_eq!( + fixture.emitter.get_backstop(), + fixture.backstop.address.clone() + ); + let event = vec![&fixture.env, fixture.env.events().all().last_unchecked()]; + assert_eq!( + event, + vec![ + &fixture.env, + ( + fixture.emitter.address.clone(), + (Symbol::new(&fixture.env, "q_swap"),).into_val(&fixture.env), + Swap { + new_backstop: new_backstop.clone(), + new_backstop_token: fixture.lp.address.clone(), + unlock_time: swap_unlock_time, + } + .into_val(&fixture.env) + ) + ] + ); + + // Let some time go by + fixture.jump(5 * 24 * 60 * 60); + + // Remove tokens from the new backstop and cancel the swap + fixture.lp.transfer(&new_backstop, &fixture.bombadil, &5); + fixture.emitter.cancel_swap_backstop(); + assert_eq!(fixture.env.auths().len(), 0); + assert_eq!( + fixture.emitter.get_backstop(), + fixture.backstop.address.clone() + ); + let event = vec![&fixture.env, fixture.env.events().all().last_unchecked()]; + assert_eq!( + event, + vec![ + &fixture.env, + ( + fixture.emitter.address.clone(), + (Symbol::new(&fixture.env, "del_swap"),).into_val(&fixture.env), + Swap { + new_backstop: new_backstop.clone(), + new_backstop_token: fixture.lp.address.clone(), + unlock_time: swap_unlock_time, + } + .into_val(&fixture.env) + ) + ] + ); + + // Restart the swap, wait for it to unlock, then swap + fixture.lp.transfer(&fixture.bombadil, &new_backstop, &5); + fixture + .emitter + .queue_swap_backstop(&new_backstop, &fixture.lp.address); + let swap_unlock_time = fixture.env.ledger().timestamp() + 31 * 24 * 60 * 60; + fixture.jump(swap_unlock_time + 1); + fixture.emitter.swap_backstop(); let event = vec![&fixture.env, fixture.env.events().all().last_unchecked()]; assert_eq!( event, @@ -86,8 +149,14 @@ fn test_emitter() { ( fixture.emitter.address.clone(), (Symbol::new(&fixture.env, "swap"),).into_val(&fixture.env), - vec![&fixture.env, new_backstop.to_val(),].into_val(&fixture.env) + Swap { + new_backstop: new_backstop.clone(), + new_backstop_token: fixture.lp.address.clone(), + unlock_time: swap_unlock_time, + } + .into_val(&fixture.env) ) ] ); + assert_eq!(fixture.emitter.get_backstop(), new_backstop.clone()); } diff --git a/test-suites/tests/test_liquidation.rs b/test-suites/tests/test_liquidation.rs index 7fccef1d..3ca6c6b1 100644 --- a/test-suites/tests/test_liquidation.rs +++ b/test-suites/tests/test_liquidation.rs @@ -99,8 +99,8 @@ fn test_liquidations() { fixture.jump(60 * 60 * 24 * 7); // Update emissions fixture.emitter.distribute(); - fixture.backstop.update_emission_cycle(); - pool_fixture.pool.update_emissions(); + fixture.backstop.gulp_emissions(); + pool_fixture.pool.gulp_emissions(); } // Start an interest auction // type 2 is an interest auction diff --git a/test-suites/tests/test_pool.rs b/test-suites/tests/test_pool.rs index 4a3ec7d2..e2a46440 100644 --- a/test-suites/tests/test_pool.rs +++ b/test-suites/tests/test_pool.rs @@ -357,8 +357,8 @@ fn test_pool_user() { // allow the rest of the emissions period to pass (6 days - 5d23h59m emitted for XLM supply) fixture.jump(6 * 24 * 60 * 60); fixture.emitter.distribute(); - fixture.backstop.update_emission_cycle(); - pool_fixture.pool.update_emissions(); + fixture.backstop.gulp_emissions(); + pool_fixture.pool.gulp_emissions(); assert_eq!(fixture.env.auths().len(), 0); // no auth required to update emissions // Sam repay and withdrawal positions diff --git a/test-suites/tests/test_wasm_happy_path.rs b/test-suites/tests/test_wasm_happy_path.rs index e0a6b250..df6256fb 100644 --- a/test-suites/tests/test_wasm_happy_path.rs +++ b/test-suites/tests/test_wasm_happy_path.rs @@ -360,13 +360,13 @@ fn test_wasm_happy_path() { 10, ); - // Let one month pass - fixture.jump(60 * 60 * 24 * 30); + // Let rest of emission period pass + fixture.jump(341940); // Distribute emissions fixture.emitter.distribute(); - fixture.backstop.update_emission_cycle(); - pool_fixture.pool.update_emissions(); + fixture.backstop.gulp_emissions(); + pool_fixture.pool.gulp_emissions(); // Frodo claim emissions let mut backstop_blnd_balance = @@ -376,7 +376,7 @@ fn test_wasm_happy_path() { .pool .claim(&frodo, &vec![&fixture.env, 0, 3], &frodo); backstop_blnd_balance -= claim_amount; - assert_eq!(claim_amount, 116731656000); + assert_eq!(claim_amount, 11673_1656000); assert_eq!( fixture.tokens[TokenIndex::BLND].balance(&fixture.backstop.address), backstop_blnd_balance @@ -385,12 +385,12 @@ fn test_wasm_happy_path() { fixture.tokens[TokenIndex::BLND].balance(&frodo), frodo_balance + claim_amount ); + let claim_amount = fixture.backstop.claim( &frodo, &vec![&fixture.env, pool_fixture.pool.address.clone()], &frodo, ); - assert_eq!(claim_amount, 420798_0000000); backstop_blnd_balance -= claim_amount; assert_eq!( @@ -414,15 +414,16 @@ fn test_wasm_happy_path() { sam_balance + claim_amount ); - // Let a year go by and call update every week - for _ in 0..52 { - // Let one week pass - fixture.jump(60 * 60 * 24 * 7); - // Update emissions - fixture.emitter.distribute(); - fixture.backstop.update_emission_cycle(); - pool_fixture.pool.update_emissions(); - } + // Let 51 weeks go by and call update to validate emissions won't get missed + fixture.jump(60 * 60 * 24 * 7 * 51); + fixture.emitter.distribute(); + fixture.backstop.gulp_emissions(); + pool_fixture.pool.gulp_emissions(); + // Allow another week go by to distribute missed emissions + fixture.jump(60 * 60 * 24 * 7); + fixture.emitter.distribute(); + fixture.backstop.gulp_emissions(); + pool_fixture.pool.gulp_emissions(); // Frodo claims a year worth of backstop emissions let mut backstop_blnd_balance = @@ -432,7 +433,7 @@ fn test_wasm_happy_path() { &vec![&fixture.env, pool_fixture.pool.address.clone()], &frodo, ); - assert_eq!(claim_amount, 22014719_9998450); //actual amount is 22014720_0000000 but get's rounded down + assert_eq!(claim_amount, 22014719_9998450); //actual amount is 22014720_0000000 but get's rounded down // 22014719_9998450 backstop_blnd_balance -= 22014719_9998450; assert_eq!( fixture.tokens[TokenIndex::BLND].balance(&fixture.backstop.address), @@ -444,7 +445,7 @@ fn test_wasm_happy_path() { .pool .claim(&frodo, &vec![&fixture.env, 0, 3], &frodo); backstop_blnd_balance -= claim_amount; - assert_eq!(claim_amount, 1073627_6928000); + assert_eq!(claim_amount, 1073628_1728000); assert_eq!( fixture.tokens[TokenIndex::BLND].balance(&fixture.backstop.address), backstop_blnd_balance @@ -455,7 +456,7 @@ fn test_wasm_happy_path() { .pool .claim(&sam, &vec![&fixture.env, 0, 3], &sam); backstop_blnd_balance -= claim_amount; - assert_eq!(claim_amount, 8361247_4076689); + assert_eq!(claim_amount, 8361251_7312500); assert_eq!( fixture.tokens[TokenIndex::BLND].balance(&fixture.backstop.address), backstop_blnd_balance From 7b13e805ff587f4a208e19d352a1470d0422b3b4 Mon Sep 17 00:00:00 2001 From: mootz12 Date: Tue, 5 Dec 2023 08:59:43 -0500 Subject: [PATCH 5/5] chore: update comments around emissions storage --- backstop/src/storage.rs | 16 +++------------- pool/src/storage.rs | 3 ++- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/backstop/src/storage.rs b/backstop/src/storage.rs index 3501933c..f282b939 100644 --- a/backstop/src/storage.rs +++ b/backstop/src/storage.rs @@ -244,16 +244,6 @@ pub fn set_user_balance(e: &Env, pool: &Address, user: &Address, balance: &UserB /// * `pool` - The pool the deposit is associated with pub fn get_pool_balance(e: &Env, pool: &Address) -> PoolBalance { let key = BackstopDataKey::PoolBalance(pool.clone()); - // if let Some(result) = e.storage().persistent().get::(&key) { - // e.storage() - // .persistent() - // .bump(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); - // result - // } else { - // // @dev: Required to use "new" to require that "pool" was deployed from the factory - // PoolBalance::new(e, pool) - // } - get_persistent_default( e, &key, @@ -362,7 +352,7 @@ pub fn set_reward_zone(e: &Env, reward_zone: &Vec
) { ); } -/// Get current emissions EPS the backstop is distributing to the pool +/// Get the current emissions accrued for the pool /// /// ### Arguments /// * `pool` - The pool @@ -371,11 +361,11 @@ pub fn get_pool_emissions(e: &Env, pool: &Address) -> i128 { get_persistent_default(e, &key, 0i128, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED) } -/// Set the current emissions EPS the backstop is distributing to the pool +/// Set the current emissions accrued for the pool /// /// ### Arguments /// * `pool` - The pool -/// * `emissions` - The eps being distributed to the pool +/// * `emissions` - The number of tokens to distribute to the pool pub fn set_pool_emissions(e: &Env, pool: &Address, emissions: i128) { let key = BackstopDataKey::PoolEmis(pool.clone()); e.storage() diff --git a/pool/src/storage.rs b/pool/src/storage.rs index b882a679..e7901ab7 100644 --- a/pool/src/storage.rs +++ b/pool/src/storage.rs @@ -557,7 +557,8 @@ pub fn get_pool_emissions(e: &Env) -> Map { /// Set the pool reserve emissions /// /// ### Arguments -/// * `emissions` - The map of emissions by reserve token id to EPS +/// * `emissions` - The map of emissions by reserve token id to share of emissions as +/// a percentage of 1e7 (e.g. 15% = 1500000) pub fn set_pool_emissions(e: &Env, emissions: &Map) { e.storage() .persistent()