From 0c93807ae1011ff60d1bcca95dc35387146612a7 Mon Sep 17 00:00:00 2001 From: "magiodev.eth" <31893902+magiodev@users.noreply.github.com> Date: Mon, 4 Sep 2023 12:11:10 +0200 Subject: [PATCH] Make execute_retry function permissionless and adjust tests accordingly (#429) Co-authored-by: nahem --- .../cl-vault/src/test_tube/cl_proptests.rs | 201 +++++++ .../contracts/lp-strategy/CHANGELOG.md | 4 +- .../proptest-regressions/proptests.txt | 1 + .../contracts/lp-strategy/src/bond.rs | 28 +- .../contracts/lp-strategy/src/execute.rs | 148 ++--- .../contracts/lp-strategy/src/helpers.rs | 26 +- .../contracts/lp-strategy/src/ibc.rs | 27 +- .../contracts/lp-strategy/src/icq.rs | 15 +- .../contracts/lp-strategy/src/msg.rs | 1 + .../contracts/lp-strategy/src/proptests.rs | 39 +- .../contracts/lp-strategy/src/queries.rs | 21 +- tests/e2e/Makefile | 23 +- tests/e2e/README.md | 23 +- tests/e2e/cases/_helpers/contract.go | 23 + .../high_liquidity/balancer_pool1.json} | 2 +- .../pools/high_liquidity/balancer_pool2.json | 7 + .../pools/high_liquidity/balancer_pool3.json | 7 + .../high_liquidity/stableswap_pool1.json} | 2 +- .../high_liquidity/stableswap_pool2.json | 7 + .../high_liquidity/stableswap_pool3.json | 7 + .../pools/low_liquidity/balancer_pool1.json | 7 + .../low_liquidity/balancer_pool2.json} | 2 +- .../pools/low_liquidity/balancer_pool3.json | 7 + .../pools/low_liquidity/stableswap_pool1.json | 7 + .../pools/low_liquidity/stableswap_pool2.json | 7 + .../pools/low_liquidity/stableswap_pool3.json | 7 + .../cases/osmosis_gauge/osmosis_gauge_test.go | 4 +- .../cases/qtransfer/qtransfer_helpers_test.go | 6 +- tests/e2e/cases/qtransfer/qtransfer_test.go | 7 +- .../wasmd_helpers_test.go | 8 +- .../wasmd_strategy_lp_deposit_test.go | 16 +- .../cases/wasmd_retry/wasmd_helpers_test.go | 166 +++++ .../wasmd_strategy_lp_retry_test.go | 567 ++++++++++++++++++ tests/e2e/dockerfiles/osmosis.Dockerfile | 2 +- tests/e2e/suite/types.go | 16 + tests/e2e/suite/wasm.go | 43 +- 36 files changed, 1272 insertions(+), 212 deletions(-) create mode 100644 smart-contracts/contracts/cl-vault/src/test_tube/cl_proptests.rs create mode 100644 tests/e2e/cases/_helpers/contract.go rename tests/e2e/cases/_utils/{sample_pool1.json => pools/high_liquidity/balancer_pool1.json} (63%) create mode 100644 tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool2.json create mode 100644 tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool3.json rename tests/e2e/cases/_utils/{sample_pool3.json => pools/high_liquidity/stableswap_pool1.json} (63%) create mode 100644 tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool2.json create mode 100644 tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool3.json create mode 100644 tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool1.json rename tests/e2e/cases/_utils/{sample_pool2.json => pools/low_liquidity/balancer_pool2.json} (64%) create mode 100644 tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool3.json create mode 100644 tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool1.json create mode 100644 tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool2.json create mode 100644 tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool3.json rename tests/e2e/cases/{wasmd => wasmd_deposit}/wasmd_helpers_test.go (93%) rename tests/e2e/cases/{wasmd => wasmd_deposit}/wasmd_strategy_lp_deposit_test.go (97%) create mode 100644 tests/e2e/cases/wasmd_retry/wasmd_helpers_test.go create mode 100644 tests/e2e/cases/wasmd_retry/wasmd_strategy_lp_retry_test.go diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/cl_proptests.rs b/smart-contracts/contracts/cl-vault/src/test_tube/cl_proptests.rs new file mode 100644 index 000000000..196ba6a4e --- /dev/null +++ b/smart-contracts/contracts/cl-vault/src/test_tube/cl_proptests.rs @@ -0,0 +1,201 @@ +#[cfg(test)] +mod tests { + use cosmwasm_std::{Coin, Decimal, Uint128}; + use cw_vault_multi_standard::VaultInfoResponse; + use osmosis_std::types::osmosis::{ + concentratedliquidity::v1beta1::{Pool, PoolsRequest}, + tokenfactory::v1beta1::QueryDenomsFromCreatorRequest, + }; + use osmosis_test_tube::{ + cosmrs::proto::traits::Message, Account, ConcentratedLiquidity, Module, TokenFactory, Wasm, + }; + + use crate::{ + msg::{ClQueryMsg, ExecuteMsg, ExtensionQueryMsg, ModifyRangeMsg, QueryMsg}, + query::{PoolResponse, UserBalanceResponse}, + test_tube::default_init, + }; + + #[test] + #[ignore] + fn multiple_deposit_withdraw_works() { + let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let wasm = Wasm::new(&app); + + let _ = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, + &[Coin::new(5_000, "uatom"), Coin::new(5_000, "uosmo")], + &alice, + ) + .unwrap(); + + let _ = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, + &[Coin::new(5_000, "uatom"), Coin::new(5_000, "uosmo")], + &alice, + ) + .unwrap(); + + let _ = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, + &[Coin::new(5_000, "uatom"), Coin::new(5_000, "uosmo")], + &alice, + ) + .unwrap(); + + let shares: UserBalanceResponse = wasm + .query( + contract_address.as_str(), + &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( + crate::msg::UserBalanceQueryMsg::UserLockedBalance { + user: alice.address(), + }, + )), + ) + .unwrap(); + assert!(!shares.balance.is_zero()); + + let _withdraw = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::Redeem { + recipient: None, + amount: shares.balance, + }, + &[], + &alice, + ) + .unwrap(); + // verify the correct execution + } + + #[test] + #[ignore] + fn single_deposit_withdraw_works() { + let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let wasm = Wasm::new(&app); + + let deposit = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::ExactDeposit { recipient: None }, + &[Coin::new(5_000, "uatom"), Coin::new(5_000, "uosmo")], + &alice, + ) + .unwrap(); + + let _mint = deposit.events.iter().find(|e| e.ty == "tf_mint").unwrap(); + + let shares: UserBalanceResponse = wasm + .query( + contract_address.as_str(), + &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( + crate::msg::UserBalanceQueryMsg::UserLockedBalance { + user: alice.address(), + }, + )), + ) + .unwrap(); + assert!(!shares.balance.is_zero()); + + let _withdraw = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::Redeem { + recipient: None, + amount: shares.balance, + }, + &[], + &alice, + ) + .unwrap(); + // verify the correct execution + } + + // #[test] + // #[ignore] + fn move_range_works() { + let (app, contract, _cl_pool_id, admin) = default_init(); + let _alice = app + .init_account(&[ + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let wasm = Wasm::new(&app); + let _result = wasm + .execute( + contract.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ModifyRange( + ModifyRangeMsg { + lower_price: Uint128::new(2), + upper_price: Uint128::new(200), + max_slippage: Decimal::permille(5), + }, + )), + &[], + &admin, + ) + .unwrap(); + } + + #[test] + #[ignore] + fn default_init_works() { + let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let wasm = Wasm::new(&app); + let cl = ConcentratedLiquidity::new(&app); + let tf = TokenFactory::new(&app); + + let pools = cl.query_pools(&PoolsRequest { pagination: None }).unwrap(); + let pool = Pool::decode(pools.pools[0].value.as_slice()).unwrap(); + + let resp = wasm + .query::( + contract_address.as_str(), + &QueryMsg::VaultExtension(ExtensionQueryMsg::ConcentratedLiquidity( + ClQueryMsg::Pool {}, + )), + ) + .unwrap(); + + assert_eq!(resp.pool_config.pool_id, pool.id); + assert_eq!(resp.pool_config.token0, pool.token0); + assert_eq!(resp.pool_config.token1, pool.token1); + + let resp = wasm + .query::(contract_address.as_str(), &QueryMsg::Info {}) + .unwrap(); + + assert_eq!(resp.tokens, vec![pool.token0, pool.token1]); + assert_eq!( + resp.vault_token, + tf.query_denoms_from_creator(&QueryDenomsFromCreatorRequest { + creator: contract_address.to_string() + }) + .unwrap() + .denoms[0] + ); + } +} diff --git a/smart-contracts/contracts/lp-strategy/CHANGELOG.md b/smart-contracts/contracts/lp-strategy/CHANGELOG.md index bbeee8f4b..19336e815 100644 --- a/smart-contracts/contracts/lp-strategy/CHANGELOG.md +++ b/smart-contracts/contracts/lp-strategy/CHANGELOG.md @@ -24,8 +24,8 @@ ### Features -- Added retry entry point to handle exit pool errors -- Added retry entry point to handle join pool errors +- Added permission-less retry entrypoint to handle exit pool errors +- Added permission-less retry entrypoint to handle join pool errors ### Bugfixes diff --git a/smart-contracts/contracts/lp-strategy/proptest-regressions/proptests.txt b/smart-contracts/contracts/lp-strategy/proptest-regressions/proptests.txt index 255c84237..f97783546 100644 --- a/smart-contracts/contracts/lp-strategy/proptest-regressions/proptests.txt +++ b/smart-contracts/contracts/lp-strategy/proptest-regressions/proptests.txt @@ -7,3 +7,4 @@ cc 18cce7ef7418ba2e32c0dc974cce3839ed3afdd23c44eeff10c7cd502b664e52 # shrinks to (claim_amount, raw_amount, owner, bond_id) = ([0, 0, 0, 0, 0, 0, 0, 0, 11673845048084930547460394097995684, 38681151403059791997234025049684207626, 275054852874891480362198002940302056891, 248004106615233309637537480600637151515, 12165567352195846609387282639635621409, 260605945814629129877776296832005137162, 332498733738228587954824791897661214933, 220466759761543005181347682445321394851, 276955038478825658651724087112112977189, 327002931333860120384022494611419106764, 147064090442518249001596779703329850197, 39564122707571826747255339378284677948, 17245431880610274106465508916866826111, 26339003119474933218657724991629946399, 82276117695411068556044142010840638542, 295008677415348915376590468404000416830, 151554982649645368787246484795119526682, 180975261261844223519603233989024384027, 168751969190378588139721758050330892898, 131638036830868649013316482547580628713, 220442041130371752953825283455113893493, 65440179817723704813041205675233168717, 302341085874258666373729283438827979429, 59484899708101852479526669734567026566, 167625816230803446734600484639624676839, 223247288265999841840523557165498328548, 271205220985832846755417320083279796734, 188428825985446459356920011698020643141, 55914548276615285259550848482812446934, 102195989075020376935386609445353607918, 121781350805295556121749379931595727418, 49381516483748229739304062049605408686, 329483797478926557991409582759842888536, 25050877269533561643188662513251020289, 184084971816401394199792095345498654512, 314553285012672846226294513049818847532, 118214012495816784551227736304553654376, 331325951165181232595902138691158403353, 229895801368881505703484862144444531032, 217114635620378362608851676834552215349, 159876936511935302157126642045367287856, 135181955021589654255626347995453150573], [227365969172255363624507819951096565724, 301233924106392053527349799565535715008, 215189084033479621725931318636708931919, 269766051904425953113532064070452847870, 61626890029710227563630958944174916236, 121643162891773300986476522338839386555, 157890849575919296860121842741320755656, 318428887894696259998226374343390699537, 150429733389995246829353939053231673536, 1309984178131570546375402682674361514, 288596204117498775793769566300644324747, 221239221463166352388729594400491553656, 148978909380753273521137273830591697007, 275538702349313158916849577919121729747, 163713187093681229043970290243259360890, 151924563830510961661700273092653868361, 148155351515033332222116696976228855043, 23040063650194206500661391710955447418, 13976091836981510172184743689715944696, 185666796634122241421163204703768333325, 31949197993783774617668624979640684182, 44236163852815662641567703574148698704, 47478877286618988935887578375249136659, 70426128900427005954075445206391113470, 140906409014291614293719794814848067102, 107269575146883060322539137018471008849, 296995157204714311405805419068824989229, 161750435276928235606415971917746751228, 130756986618466217781348998859419151606, 105073686713080197476347255702114582082, 129373854793859197217362919487938017347, 285793942650403489640653646346783262704, 176448516216785083528112658599394780511, 200568359946301769234774760267636045793, 27117563929292709236084290248168245624, 132398191031838449046483348706831060869, 57516945481821405412092628600363321519, 197427381802548279413420705792351222501, 222846337535771089032432325957371072165, 79516707543765823940780266761219013517, 332746313329984099982163571762137691017, 297346398595796224323092462643166162137, 337071417935433974216532431467712569575, 41085095792457838701721359791557450918, 702470706316076453758134797401550118, 100190323026370930887276274578630211218, 54132925651950671821377657598243746105, 160055808294617960457289618967505797919, 112719553546396289399469348203275740183, 242448663799866037822921626508547285315], ["pelxgcgce", "eypufagrnmxaahmtwbz", "cksckqcgu", "johwpajdhxxdilpdqrskizhddv", "ecdyivkeowdhfpw", "ueuajshhjamecwhwunb", "ytwfxzlqv", "hbbmwqnofuhemyeziytmbdf", "ygqznvztityjg", "ntflh", "mivqrepqpwb", "gdvidjpuxwlrrqbr", "xjarmvwsgdwvwbblsooirfpcc", "zepkhzdkhvuakrhxsevrtdmbethihul", "ifrbn", "mersihvlcbvl", "ofyoghkarnxgpkorimi", "drdnbwlazpjrnmjpsksn", "mlppcbqtffsanomjokhth", "rxvlxdmxxlhckfklgd", "abf", "xltocqunoj", "ivsethz", "nzxsbgopozgqdxdmjmahmlglzwppoqg", "tkfsavgckbbzenoy", "sszuzdirsyfcwqogzhcnoaku", "rqnynflkonzpf", "kxlbd", "sylpzasamyg", "arbxqxavnptpewgb", "zovsuusznqywcnmqjppgzhwlcl", "pvjqbwupe", "xlnxdjtvxpd", "", "xbdygipeqfbazl", "j", "mdxudvywbvbfxkhnq", "ijrbpwsmvhzksyztnbqctyfqoiwkcy", "dqthiwptekttiplbhttgiuj", "twqkimydeuvacaelnklsxsbavmobuiqk", "qixstkpwyugrfwxsgohjpvtaa", "rvsiw", "ghwgcnggijoyapcaadxjk", "vkjv", "icpqzohhbpiywrdonllmzadjmffin", "ridgzcfb", "xbeunufvpqzytneqr", "cgoazeqiogbsv", "pprfeonetfysfbylr", "w"], [245156543980833747090242765144329311038, 194567896024102730141520967076722737253, 78321590831082444086273322095061857666, 46234274478596011116062623468273869668, 263465158450098859717724367516067905975, 100752541069266103352244815706779144613, 246329798443977159630785364311541203165, 89071066193382807867647247777458784703, 89680324833787488426590379191240982648, 326751368326903363754619742229709084471, 81949509939543357493869595004811069397, 320435057684033242953952509794605645127, 133212895719388354112667050350502201843, 262256541475020683812327300478510544022, 68423813730476634270402958635073934367, 156738226689669755944851331748791005616, 165800725532545879609977936210035055040, 172018455739485735148072644379216290447, 230951541362177602359868002660805912181, 152287027695533706215823402052033189960, 185057933550094735087283806786823061589, 122688000701961209416344853008164886381, 134958802702793640081845590040343954920, 234580960409424622564273814575333116288, 183739892098724792237695657764961499807, 39015994699012683337617007960068058843, 324137172833870104374658339409853260398, 156871594659100780158350555737480367369, 76549132260116470292478935610594931831, 206367556367473756044436720233354369995, 242121355820534272849028361122887112128, 188631998326034004600861498586852938791, 216069174770114465624174207718872444354, 307944890529036529511929496919799050224, 316346783727245703493409702940347462603, 232794123719215275741536507199048918940, 230699271415462493000400243339292494907, 215746109799468672448745109724428767505, 10735964370838476891653111627115651590, 177546680977013945994944742461442930075, 48188853863336799882618830643360361251, 123072710083781474823580256603344815581, 53696605966638634884824007767847163895, 244813750508221881267886759962353770844, 138389416756738828485107907845869178387, 28787808414027521881066099129863372506, 208960876676490369283588392054850944689, 291922470923877549823217120675909351267, 115223001020919711020256788413332787629, 309151305555635829791059270121082705015]) cc ed5adefd853fbe7386e63b9d852d81fcfb66fd30eb1ee0ac95f4915dc32af26d # shrinks to (claim_amount, raw_amount, owner, bond_id) = ([0, 0], [5567553736818050811761864155695314435, 334714813184120412651612743276072882021], ["", ""], [0, 0]) cc bb29a873ab429224989abbadbb61fd8492c0c147fcbfdd7c03eb64d853c254db # shrinks to (claim_amount, raw_amount, owner, bond_id) = ([], [], [], []), (amount_pd, owner_pd, bond_id_pd) = ([], [], []), raw_balalance_rq = 0, quote_balance_rq = 0, lp_balance_rq = 0, join_pool_rq = 0, exit_pool_base_rq = 0, exit_pool_quote_rq = 0, spot_price_rq = 0 +cc 34775cc5f6b44bd958d663493908f0e5546434ed9156d1ebe18a1503b4166df0 # shrinks to (claim_amount, raw_amount, owner, bond_id) = ([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 83553156567498, 11808849592816811342, 8130787376546610459, 14980519244902503230, 14234410997718520343, 10225549382265082313, 14258484066235613374, 16132509078925444533, 12496179773926113447, 5163701959177179557, 6115732151642180644, 16014557328226574517, 17910735935150482215, 15905882731006320604, 6938858134545344839, 311364060211843560, 8464979079766962920, 8688102988532725869, 6950140404087665517, 61793720827537470, 15697647606715928957, 6549806696217709345, 6362990510017777241, 10957519013857898322, 12513697704065533392, 10682377085392477145, 11467648984684906402, 6914635055688605728, 6355149797117565939, 3759358825474970803, 1512101291444601349, 12542255319701031943, 7005223357316672524, 11709985910294196329, 16431675148878993558, 18088527547444258371, 18128405577901023348, 533460672940784407, 6547510708312023640, 12866793111322861531, 8672995590736397231, 14424622615502074446, 2307613407971382985, 17441942398532076118], [15826457340195732357, 3704826055518850654, 7534652765877672533, 17446234302595466983, 6161974760894201947, 14625587653503619871, 6368026972723040756, 9429109580945985830, 2272938497016214323, 3904354027872436016, 11765354634249073970, 5206622489089162589, 6352158132024455001, 10360948853066292607, 16091076371184297087, 10686302784989918058, 522004517581656444, 4357444334841157825, 8752957838480279932, 1966713813215548561, 16954393754388505823, 984025542946232675, 13665913500959464705, 4955554490241156322, 7294307173539117479, 938763324680373732, 492520615853963630, 14849790482556102869, 13517356408982302285, 12639950288329634165, 13393035703502329928, 16572484674960023979, 6573211058722160462, 14412302373624951367, 6188123385003315881, 11715869108671089008, 5648724300247408734, 5413705664772579881, 5998831249611394040, 1220625596522501898, 1377604803059093507, 141220414108167420, 120942024884388456, 15465952658707590747, 14335606127926440686, 16175674822280915491, 8695491072012231849, 1853068128626976492, 11227189535675615227, 4960793297193568795, 7850650787850785584, 2911908877680041078, 9878681945393343004, 7725132537583460349, 5630970625065211239, 15588629860098892381, 9561298833016379036, 17094307055106940528, 6013650033902031667, 8259839850861701315], ["dgrqiyjcovaookyblrg", "ztmagivlipvydahetyhpdwayykzjj", "tfdbxmfojhsbmzgumapeoz", "gxezjehqzrkzutmghdlqaxfduyvdtmmd", "fhsdpym", "wudbifslwjvruodwlokezlrkouqarvt", "zpnihv", "e", "vkyycuyhdzzdeemvpigsb", "plvetqyxmjjhsgtuysxqqymj", "inwzsdwpbdoerg", "zfwpkflklyt", "mnbfxebcyiailqsrfqowr", "mupbksylxtjtgqalnitlqjitlnzks", "qpjyoai", "bzskbtibcesvxnfgbvuzhzgtycy", "opjjotortcawlyttck", "mgftscgljwawrffmgsffsxleujngatbi", "atofntcuqdxmzqyaykhwyuwzy", "bkehgtfwnxphyxfaryescclprn", "moaianym", "hufolvhlfdjbeogauquuv", "dpkoobsneuuktmrxabwhk", "fxkeasrmkldatftoxaxtrow", "ynqjoxyleuoo", "dmjobjvtdxvfaiiicbjjpmhrke", "rpqahmnaflpqteyeyzfbhuab", "qgxytkazzaowoahejafngnnmoyuwq", "mctubnxzqodnwqxlhfhcrmtnoqaav", "nhjqjrktvc", "ysyqcjioyrfhutkaueaoocfxnctqazeq", "clxcviqulqfkm", "rtnwqgagvt", "jmqaltldcjcvfmdj", "nnlnpocqf", "pptjwiqwjbxfetnztectxrainddacm", "onxfua", "jngvifuoigb", "scmknna", "splnseom", "onqlrofyfmycjjdz", "fifjawebeqjtnbvoeioyimxvjjl", "ovqeftnbpqwp", "sk", "zidmgenlzppemiofhewmlsefjzdo", "rweenuisuhqk", "hop", "hpnyurozekfedbxhzidxbfihcypeljuf", "x", "ttpwuzzlnw", "defee", "pszdtgzfkzdxldlcrbr", "hqqnyylxmm", "ytwhdpgbqqluofeimednsepsvkxhaae", "uwlzmmgbadlymz", "ilpfthwgblxrlcwt", "trxqrxeii", "nklgitkbqdgbuiykweimoxxbus", "wsnxygctcnomzgjywfvtu", "bfcglnnizuhprogakd"], [17564701645947559694, 10684717952737285943, 10219761788279915146, 2109710357599859391, 10487310863879864593, 15822884041780719907, 11848105584455790133, 17931449780282279864, 8909590184269171323, 2959034442329770038, 16596193355103071593, 15485371830524286297, 15155403646891693209, 16618642512816226160, 11042324674626444498, 11974407949461512535, 1763186450440707960, 18290373659633014915, 15628661186771988252, 16836233893596365339, 16156643523011845388, 6812953976141217730, 3685585248911984394, 810033298279336584, 9998175313336111059, 8687288352342553603, 3098556134784640099, 3864774701428716149, 15051486419399376922, 2190077543700623599, 16012320347463077713, 16833307165034632153, 2395940186996495483, 4130047742181157138, 398807872624232724, 5593681726988201550, 17694081960095301824, 15985889760645175226, 16376128475035724969, 4026056742303909744, 15014143413475248664, 3995771739911710916, 1941983340358743336, 13206881376468297167, 14576866369417099042, 11358921675486065870, 2026942581626530092, 3182543356739401115, 13832536031591260330, 10929954346159679353, 17226923001376597991, 14359302255407025404, 15604591261117489966, 4652729393292511177, 14178413536313939160, 8962255765512619945, 11954228840537278358, 7081160827464255778, 12740476215904686133, 901276637696619765]), (amount_pd, owner_pd, bond_id_pd) = ([], [], []), raw_balalance_rq = 3906736672086151414, quote_balance_rq = 5260586167652135091, lp_balance_rq = 47453939318407458, join_pool_rq = 17437550474192041343, exit_pool_base_rq = 12672570040552928040, exit_pool_quote_rq = 261663182347622709, spot_price_rq = 10002155156807470437 diff --git a/smart-contracts/contracts/lp-strategy/src/bond.rs b/smart-contracts/contracts/lp-strategy/src/bond.rs index 5a9214894..941a2e89e 100644 --- a/smart-contracts/contracts/lp-strategy/src/bond.rs +++ b/smart-contracts/contracts/lp-strategy/src/bond.rs @@ -73,14 +73,18 @@ pub fn batch_bond( let to_address = get_ica_address(storage, ICA_CHANNEL.load(storage)?)?; if let Some((amount, deposits)) = fold_bonds(storage, total_vault_value)? { - Ok(Some(do_transfer( - storage, - env, - amount, - transfer_chan, - to_address, - deposits, - )?)) + if amount.is_zero() { + Ok(None) + } else { + Ok(Some(do_transfer( + storage, + env, + amount, + transfer_chan, + to_address, + deposits, + )?)) + } } else { Ok(None) } @@ -128,7 +132,7 @@ pub fn fold_bonds( FAILED_JOIN_QUEUE .pop_front(storage)? .ok_or(ContractError::QueueItemNotFound { - queue: "bond".to_string(), + queue: "failed_join".to_string(), })?; let claim_amount = create_claim( storage, @@ -137,9 +141,7 @@ pub fn fold_bonds( &item.bond_id, total_balance, )?; - total = total - .checked_add(item.amount) - .map_err(|err| ContractError::TracedOverflowError(err, "fold_bonds".to_string()))?; + REJOIN_QUEUE.push_back( storage, &OngoingDeposit { @@ -155,7 +157,7 @@ pub fn fold_bonds( } // create_claim -fn create_claim( +pub fn create_claim( storage: &mut dyn Storage, user_balance: Uint128, address: &Addr, diff --git a/smart-contracts/contracts/lp-strategy/src/execute.rs b/smart-contracts/contracts/lp-strategy/src/execute.rs index 1932df29e..fb850de32 100644 --- a/smart-contracts/contracts/lp-strategy/src/execute.rs +++ b/smart-contracts/contracts/lp-strategy/src/execute.rs @@ -2,7 +2,6 @@ use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use cw_utils::nonpayable; use crate::{ - admin::is_lock_admin, bond::Bond, error::ContractError, helpers::{IbcMsgKind, IcaMessages}, @@ -21,9 +20,7 @@ pub fn execute_retry( channel: String, ) -> Result { nonpayable(&info)?; - // for now, only the lock admin can retry - is_lock_admin(deps.storage, &deps.querier, &env, &info.sender)?; - + // this endpoint is permissionless in order to allow anyone trigger a retry let traps = TRAPS.load(deps.storage, (seq, channel.clone()))?; match traps.step { IbcMsgKind::Ica(ica_kind) => match ica_kind { @@ -57,7 +54,7 @@ pub fn handle_retry_join_pool( deps.storage, &Bond { amount, - owner: ongoing_deposit.owner, + owner: ongoing_deposit.owner.clone(), bond_id: ongoing_deposit.bond_id.clone(), }, )?; @@ -121,7 +118,7 @@ mod tests { use cosmwasm_std::{ attr, testing::{mock_dependencies, mock_env}, - to_binary, Addr, Empty, StdError, Timestamp, Uint128, + to_binary, Addr, StdError, Timestamp, Uint128, }; use osmosis_std::types::osmosis::gamm::v1beta1::{ QueryCalcExitPoolCoinsFromSharesResponse, QueryCalcJoinPoolSharesResponse, @@ -130,15 +127,14 @@ mod tests { use quasar_types::icq::{CosmosResponse, InterchainQueryPacketAck}; use crate::ibc::handle_icq_ack; - use crate::state::PENDING_BOND_QUEUE; + use crate::state::{PENDING_BOND_QUEUE, REJOIN_QUEUE, SIMULATED_JOIN_AMOUNT_IN}; use crate::test_helpers::{create_query_response, pending_bond_to_bond}; use crate::{ contract::execute_try_icq, error::Trap, ibc_lock::Lock, state::{ - OngoingDeposit, RawAmount, Unbond, IBC_LOCK, LOCK_ADMIN, PENDING_UNBOND_QUEUE, - UNBONDING_CLAIMS, + OngoingDeposit, RawAmount, Unbond, IBC_LOCK, PENDING_UNBOND_QUEUE, UNBONDING_CLAIMS, }, test_helpers::default_setup, unbond::ReturningUnbond, @@ -183,10 +179,6 @@ mod tests { ) .unwrap(); - LOCK_ADMIN - .save(deps.as_mut().storage, &Addr::unchecked("admin"), &Empty {}) - .unwrap(); - UNBONDING_CLAIMS .save( deps.as_mut().storage, @@ -233,7 +225,7 @@ mod tests { deps.as_mut(), env, MessageInfo { - sender: Addr::unchecked("admin"), + sender: Addr::unchecked("not_admin"), funds: vec![], }, 3539, @@ -289,10 +281,6 @@ mod tests { ) .unwrap(); - LOCK_ADMIN - .save(deps.as_mut().storage, &Addr::unchecked("admin"), &Empty {}) - .unwrap(); - UNBONDING_CLAIMS .save( deps.as_mut().storage, @@ -311,7 +299,7 @@ mod tests { deps.as_mut(), env, MessageInfo { - sender: Addr::unchecked("admin"), + sender: Addr::unchecked("not_admin"), funds: vec![], }, 3539, @@ -355,10 +343,6 @@ mod tests { ) .unwrap(); - LOCK_ADMIN - .save(deps.as_mut().storage, &Addr::unchecked("admin"), &Empty {}) - .unwrap(); - UNBONDING_CLAIMS .save( deps.as_mut().storage, @@ -377,7 +361,7 @@ mod tests { deps.as_mut(), env, MessageInfo { - sender: Addr::unchecked("admin"), + sender: Addr::unchecked("not_admin"), funds: vec![], }, 3539, @@ -400,29 +384,6 @@ mod tests { ); } - #[test] - fn test_handle_retry_exit_pool_as_not_admin_fails() { - let mut deps = mock_dependencies(); - let env = mock_env(); - - LOCK_ADMIN - .save(deps.as_mut().storage, &Addr::unchecked("admin"), &Empty {}) - .unwrap(); - - let res = execute_retry( - deps.as_mut(), - env, - MessageInfo { - sender: Addr::unchecked("not_admin"), - funds: vec![], - }, - 3539, - "channel-35".to_string(), - ); - - assert!(res.is_err()); - } - #[test] fn test_handle_retry_exit_pool_with_wrong_seq_channel_fails() { let mut deps = mock_dependencies(); @@ -455,15 +416,11 @@ mod tests { ) .unwrap(); - LOCK_ADMIN - .save(deps.as_mut().storage, &Addr::unchecked("admin"), &Empty {}) - .unwrap(); - let res = execute_retry( deps.as_mut(), env, MessageInfo { - sender: Addr::unchecked("admin"), + sender: Addr::unchecked("not_admin"), funds: vec![], }, 0, @@ -510,10 +467,6 @@ mod tests { ) .unwrap(); - LOCK_ADMIN - .save(deps.as_mut().storage, &Addr::unchecked("admin"), &Empty {}) - .unwrap(); - UNBONDING_CLAIMS .save( deps.as_mut().storage, @@ -560,7 +513,7 @@ mod tests { deps.as_mut(), env.clone(), MessageInfo { - sender: Addr::unchecked("admin"), + sender: Addr::unchecked("not_admin"), funds: vec![], }, 3539, @@ -601,7 +554,7 @@ mod tests { deps.as_mut(), env, MessageInfo { - sender: Addr::unchecked("admin"), + sender: Addr::unchecked("not_admin"), funds: vec![], }, 3539, @@ -619,10 +572,6 @@ mod tests { IBC_LOCK.save(deps.as_mut().storage, &Lock::new()).unwrap(); - LOCK_ADMIN - .save(deps.as_mut().storage, &Addr::unchecked("admin"), &Empty {}) - .unwrap(); - // mock the failed join pool trap with 3 bonds let failed = PendingBond { bonds: vec![ @@ -664,7 +613,7 @@ mod tests { deps.as_mut(), env.clone(), MessageInfo { - sender: Addr::unchecked("admin"), + sender: Addr::unchecked("not_admin"), funds: vec![], }, 3539, @@ -804,31 +753,22 @@ mod tests { let res = handle_icq_ack(deps.as_mut().storage, env, to_binary(&ibc_ack).unwrap()).unwrap(); - // get the failed pending bonds total amount - let pending_total_amount = failed.bonds.iter().fold(Uint128::zero(), |acc, bond| { - let amount = match bond.raw_amount { - RawAmount::LocalDenom(amount) => amount, - RawAmount::LpShares(_) => panic!("unexpected lp shares"), - }; - acc + amount - }); + // we do NOT transfer any token here, failed bonds were already transferred to the contract before failing and stay there + // as we do not have any bond_queue items, we return None here + assert!(res.messages.is_empty()); - // check that the res amount matches the amount in the failed join queue - match &res.messages[0].msg { - CosmosMsg::Ibc(IbcMsg::Transfer { amount, .. }) => { - assert_eq!( - amount, - &Coin { - denom: "ibc/local_osmo".to_string(), - amount: pending_total_amount, - } - ); - } - _ => panic!("unexpected message type"), - } + assert!(TRAPS.is_empty(deps.as_ref().storage)); - // check that the failed join queue is emptied - assert!(FAILED_JOIN_QUEUE.is_empty(&deps.storage).unwrap()); + // FAILED_JOIN_QUEUE should be empty and all bonds moved to REJOIN_QUEUE + assert_eq!(FAILED_JOIN_QUEUE.len(&deps.storage).unwrap(), 0); + assert_eq!(REJOIN_QUEUE.len(&deps.storage).unwrap(), 3); + + assert_eq!( + SIMULATED_JOIN_AMOUNT_IN + .load(deps.as_ref().storage) + .unwrap(), + Uint128::new(1000 + 999 + 1000) + ); } #[test] @@ -839,27 +779,23 @@ mod tests { IBC_LOCK.save(deps.as_mut().storage, &Lock::new()).unwrap(); - LOCK_ADMIN - .save(deps.as_mut().storage, &Addr::unchecked("admin"), &Empty {}) - .unwrap(); - // mock the failed join pool trap with 3 bonds let failed = PendingBond { bonds: vec![ OngoingDeposit { - claim_amount: Uint128::new(100), + claim_amount: Uint128::new(1000), raw_amount: RawAmount::LocalDenom(Uint128::new(1000)), owner: Addr::unchecked("address"), bond_id: "1".to_string(), }, OngoingDeposit { - claim_amount: Uint128::new(99), + claim_amount: Uint128::new(999), raw_amount: RawAmount::LocalDenom(Uint128::new(999)), owner: Addr::unchecked("address"), bond_id: "2".to_string(), }, OngoingDeposit { - claim_amount: Uint128::new(101), + claim_amount: Uint128::new(1000), raw_amount: RawAmount::LocalDenom(Uint128::new(1000)), owner: Addr::unchecked("address"), bond_id: "3".to_string(), @@ -880,7 +816,7 @@ mod tests { .unwrap(); // mock pending deposits and add them to the pending queue - let pedning_bonds = vec![ + let pending_bonds = vec![ Bond { amount: Uint128::new(5_000), owner: Addr::unchecked("address"), @@ -893,7 +829,7 @@ mod tests { }, ]; - for bond in pedning_bonds.iter() { + for bond in pending_bonds.iter() { PENDING_BOND_QUEUE .push_back(deps.as_mut().storage, bond) .unwrap(); @@ -904,7 +840,7 @@ mod tests { deps.as_mut(), env.clone(), MessageInfo { - sender: Addr::unchecked("admin"), + sender: Addr::unchecked("not_admin"), funds: vec![], }, 3539, @@ -1027,28 +963,19 @@ mod tests { let res = handle_icq_ack(deps.as_mut().storage, env, to_binary(&ibc_ack).unwrap()).unwrap(); - // get the failed pending bonds total amount - let failed_total_amount = failed.bonds.iter().fold(Uint128::zero(), |acc, bond| { - let amount = match bond.raw_amount { - RawAmount::LocalDenom(amount) => amount, - RawAmount::LpShares(_) => panic!("unexpected lp shares"), - }; - acc + amount - }); - // get the pending bonds total amount - let pending_total_amount = pedning_bonds + let pending_total_amount = pending_bonds .iter() .fold(Uint128::zero(), |acc, bond| acc + bond.amount); - // check that the res amount matches the amount in both queues + // check that the res amount matches the amount in the pending queue ONLY match &res.messages[0].msg { CosmosMsg::Ibc(IbcMsg::Transfer { amount, .. }) => { assert_eq!( amount, &Coin { denom: "ibc/local_osmo".to_string(), - amount: failed_total_amount + pending_total_amount, + amount: pending_total_amount, } ); } @@ -1058,5 +985,10 @@ mod tests { // check that the failed join & pending queues are emptied assert!(FAILED_JOIN_QUEUE.is_empty(&deps.storage).unwrap()); assert!(PENDING_BOND_QUEUE.is_empty(&deps.storage).unwrap()); + + // failed bonds should be now in the REJOIN_QUEUE + let rejoin_queue: Result, StdError> = + REJOIN_QUEUE.iter(&deps.storage).unwrap().collect(); + assert_eq!(failed.bonds, rejoin_queue.unwrap()); } } diff --git a/smart-contracts/contracts/lp-strategy/src/helpers.rs b/smart-contracts/contracts/lp-strategy/src/helpers.rs index 789a4f551..f71ec8419 100644 --- a/smart-contracts/contracts/lp-strategy/src/helpers.rs +++ b/smart-contracts/contracts/lp-strategy/src/helpers.rs @@ -4,8 +4,9 @@ use crate::{ ibc_lock::Lock, msg::ExecuteMsg, state::{ - PendingBond, PendingSingleUnbond, RawAmount, BOND_QUEUE, CHANNELS, CONFIG, IBC_LOCK, - REPLIES, SHARES, START_UNBOND_QUEUE, TRAPS, UNBOND_QUEUE, + PendingBond, PendingSingleUnbond, RawAmount, BOND_QUEUE, CHANNELS, CONFIG, + FAILED_JOIN_QUEUE, IBC_LOCK, REJOIN_QUEUE, REPLIES, SHARES, START_UNBOND_QUEUE, TRAPS, + UNBOND_QUEUE, }, unbond::PendingReturningUnbonds, }; @@ -130,7 +131,22 @@ pub fn get_usable_compound_balance( // two cases where we exclude funds, either transfer succeeded, but not ica, or transfer succeeded and subsequent ica failed let traps = TRAPS.range(storage, None, None, Order::Ascending); - let excluded_funds = traps.fold(Uint128::zero(), |acc, wrapped_trap| { + let failed_join_queue_amount = FAILED_JOIN_QUEUE.iter(storage)?.try_fold( + Uint128::zero(), + |acc, val| -> Result { Ok(acc + val?.amount) }, + )?; + + let rejoin_queue_amount = REJOIN_QUEUE.iter(storage)?.try_fold( + Uint128::zero(), + |acc, val| -> Result { + match val?.raw_amount { + crate::state::RawAmount::LocalDenom(amount) => Ok(amount + acc), + crate::state::RawAmount::LpShares(_) => Err(ContractError::IncorrectRawAmount), + } + }, + )?; + + let trapped_errors_amount = traps.fold(Uint128::zero(), |acc, wrapped_trap| { let trap = wrapped_trap.unwrap().1; if trap.last_succesful { if let IbcMsgKind::Transfer { pending: _, amount } = trap.step { @@ -152,6 +168,10 @@ pub fn get_usable_compound_balance( } }); + let excluded_funds = failed_join_queue_amount + .checked_add(rejoin_queue_amount)? + .checked_add(trapped_errors_amount)?; + Ok(balance.saturating_sub(excluded_funds)) } diff --git a/smart-contracts/contracts/lp-strategy/src/ibc.rs b/smart-contracts/contracts/lp-strategy/src/ibc.rs index 4d52ca203..21e6e57d0 100644 --- a/smart-contracts/contracts/lp-strategy/src/ibc.rs +++ b/smart-contracts/contracts/lp-strategy/src/ibc.rs @@ -13,10 +13,10 @@ use crate::ibc_util::{ use crate::icq::calc_total_balance; use crate::start_unbond::{batch_start_unbond, handle_start_unbond_ack}; use crate::state::{ - LpCache, OngoingDeposit, PendingBond, CHANNELS, CONFIG, IBC_LOCK, IBC_TIMEOUT_TIME, - ICA_CHANNEL, ICQ_CHANNEL, LP_SHARES, OSMO_LOCK, PENDING_ACK, RECOVERY_ACK, REJOIN_QUEUE, - SIMULATED_EXIT_RESULT, SIMULATED_EXIT_SHARES_IN, SIMULATED_JOIN_AMOUNT_IN, - SIMULATED_JOIN_RESULT, TIMED_OUT, TOTAL_VAULT_BALANCE, TRAPS, USABLE_COMPOUND_BALANCE, + LpCache, PendingBond, CHANNELS, CONFIG, IBC_LOCK, IBC_TIMEOUT_TIME, ICA_CHANNEL, ICQ_CHANNEL, + LP_SHARES, OSMO_LOCK, PENDING_ACK, RECOVERY_ACK, REJOIN_QUEUE, SIMULATED_EXIT_RESULT, + SIMULATED_EXIT_SHARES_IN, SIMULATED_JOIN_AMOUNT_IN, SIMULATED_JOIN_RESULT, TIMED_OUT, + TOTAL_VAULT_BALANCE, TRAPS, USABLE_COMPOUND_BALANCE, }; use crate::unbond::{batch_unbond, transfer_batch_unbond, PendingReturningUnbonds}; use cosmos_sdk_proto::cosmos::bank::v1beta1::QueryBalanceResponse; @@ -47,7 +47,7 @@ use cosmwasm_std::{ from_binary, to_binary, Attribute, Binary, Coin, CosmosMsg, Decimal, DepsMut, Env, IbcBasicResponse, IbcChannel, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, IbcTimeout, - QuerierWrapper, Response, StdError, StdResult, Storage, SubMsg, Uint128, WasmMsg, + QuerierWrapper, Response, StdError, Storage, SubMsg, Uint128, WasmMsg, }; /// enforces ordering and versioning constraints, this combines ChanOpenInit and ChanOpenTry @@ -308,7 +308,7 @@ pub fn handle_transfer_ack( let share_out_min_amount = calculate_share_out_min_amount(storage)?; - let failed_bonds_amount = REJOIN_QUEUE.iter(storage)?.try_fold( + let rejoin_queue_amount = REJOIN_QUEUE.iter(storage)?.try_fold( Uint128::zero(), |acc, val| -> Result { match val?.raw_amount { @@ -324,13 +324,15 @@ pub fn handle_transfer_ack( // a better fix would be to get the last state of the pool and do the math on our side to then also be able to include this USABLE_COMPOUND_BALANCE // howver, if we are going to deprecate this vault, I'd argue it's not worth it - seeing as the compound balance would just be "extra money" for a user anyway // TODO: remove this comment - let usable_base_token_compound_balance = USABLE_COMPOUND_BALANCE.load(storage)?; + let base_token_rewards = USABLE_COMPOUND_BALANCE.load(storage)?; - let total_amount = - transferred_amount + failed_bonds_amount + usable_base_token_compound_balance; + let total_amount = transferred_amount + rejoin_queue_amount + base_token_rewards; - let pending_rejoins: StdResult> = REJOIN_QUEUE.iter(storage)?.collect(); - pending.bonds.append(&mut pending_rejoins?); + // remove all items from REJOIN_QUEUE & add them to the deposits: Vec + while !REJOIN_QUEUE.is_empty(storage)? { + let rejoin = REJOIN_QUEUE.pop_front(storage)?; + pending.bonds.push(rejoin.unwrap()); + } let msg = do_ibc_join_pool_swap_extern_amount_in( storage, @@ -483,7 +485,6 @@ pub fn handle_icq_ack( TOTAL_VAULT_BALANCE.save(storage, &total_balance)?; - // TODO: decide if we use exit_total_pool or the UNBOND_QUEUE added amount here let parsed_exit_pool_out = consolidate_exit_pool_amount_into_local_denom( storage, &exit_pool_unbonds.tokens_out, @@ -496,7 +497,7 @@ pub fn handle_icq_ack( SIMULATED_EXIT_RESULT.save(storage, &parsed_exit_pool_out)?; // todo move this to below into the lock decisions - let bond = batch_bond(storage, &env, total_balance)?; + let bond: Option = batch_bond(storage, &env, total_balance)?; let mut msges = Vec::new(); let mut attrs = Vec::new(); diff --git a/smart-contracts/contracts/lp-strategy/src/icq.rs b/smart-contracts/contracts/lp-strategy/src/icq.rs index c3d96baeb..c73462718 100644 --- a/smart-contracts/contracts/lp-strategy/src/icq.rs +++ b/smart-contracts/contracts/lp-strategy/src/icq.rs @@ -1,6 +1,5 @@ use cosmwasm_std::{ - to_binary, Decimal, Env, Fraction, IbcMsg, IbcTimeout, QuerierWrapper, StdError, Storage, - SubMsg, Uint128, + to_binary, Decimal, Env, Fraction, IbcMsg, IbcTimeout, QuerierWrapper, Storage, SubMsg, Uint128, }; use osmosis_std::types::{ cosmos::{bank::v1beta1::QueryBalanceRequest, base::v1beta1::Coin as OsmoCoin}, @@ -58,15 +57,17 @@ pub fn try_icq( } } - let failed_bonds_amount = FAILED_JOIN_QUEUE - .iter(storage)? - .try_fold(Uint128::zero(), |acc, val| -> Result { + let failed_join_queue_amount = FAILED_JOIN_QUEUE.iter(storage)?.try_fold( + Uint128::zero(), + |acc, val| -> Result { Ok(acc + val?.amount) - })?; + // We should never have LP shares here + }, + )?; // the bonding amount that we want to calculate the slippage for is the amount of funds in new bonds and the amount of funds that have // previously failed to join the pool. These funds are already located on Osmosis and should not be part of the transfer to Osmosis. - let bonding_amount = pending_bonds_value + failed_bonds_amount; + let bonding_amount = pending_bonds_value + failed_join_queue_amount; // we dump pending unbonds into the active unbond queue and save the total amount of shares that will be unbonded let mut pending_unbonds_shares = Uint128::zero(); diff --git a/smart-contracts/contracts/lp-strategy/src/msg.rs b/smart-contracts/contracts/lp-strategy/src/msg.rs index 05d5de976..14a82d17b 100644 --- a/smart-contracts/contracts/lp-strategy/src/msg.rs +++ b/smart-contracts/contracts/lp-strategy/src/msg.rs @@ -100,6 +100,7 @@ pub struct GetQueuesResponse { pub unbond_queue: Vec, pub failed_join_queue: Vec, pub rejoin_queue: Vec, + pub pending_unbond_queue: Vec, } #[cw_serde] diff --git a/smart-contracts/contracts/lp-strategy/src/proptests.rs b/smart-contracts/contracts/lp-strategy/src/proptests.rs index a31452ed9..c51ef0cae 100644 --- a/smart-contracts/contracts/lp-strategy/src/proptests.rs +++ b/smart-contracts/contracts/lp-strategy/src/proptests.rs @@ -19,7 +19,7 @@ mod tests { ibc_lock::Lock, state::{ OngoingDeposit, PendingBond, RawAmount, FAILED_JOIN_QUEUE, IBC_LOCK, LOCK_ADMIN, - PENDING_BOND_QUEUE, TRAPS, + PENDING_BOND_QUEUE, REJOIN_QUEUE, TRAPS, }, test_helpers::{create_query_response, default_setup, pending_bond_to_bond}, }; @@ -30,8 +30,7 @@ mod tests { }, }; use osmosis_std::types::{ - cosmos::base::v1beta1::Coin as OsmoCoin, - osmosis::{gamm::v2::QuerySpotPriceResponse, lockup::LockedResponse}, + cosmos::base::v1beta1::Coin as OsmoCoin, osmosis::gamm::v2::QuerySpotPriceResponse, }; use proptest::collection::vec; @@ -79,9 +78,9 @@ mod tests { // mock the failed join pool trap with 3 bonds let failed = PendingBond { - bonds: claim_amount.iter().zip(&raw_amount).zip(&owner).zip(&bond_id).map(|(((claim, raw), owner), id)| { + bonds: claim_amount.iter().zip(&raw_amount).zip(&owner).zip(&bond_id).map(|(((_claim, raw), owner), id)| { OngoingDeposit { - claim_amount: Uint128::new(*claim), + claim_amount: Uint128::new(*raw), raw_amount: RawAmount::LocalDenom(Uint128::new(*raw)), owner: Addr::unchecked(owner), bond_id: id.to_string(), @@ -248,21 +247,12 @@ mod tests { // simulate that we received the ICQ ACK let res = handle_icq_ack(deps.as_mut().storage, env, to_binary(&ibc_ack).unwrap()).unwrap(); - // get the failed pending bonds total amount - let failed_total_amount = failed.bonds.iter().fold(Uint128::zero(), |acc, bond| { - let amount = match bond.raw_amount { - RawAmount::LocalDenom(amount) => amount, - RawAmount::LpShares(_) => panic!("unexpected lp shares"), - }; - acc + amount - }); - // get the pending bonds total amount let pending_total_amount = pending_bonds.iter().fold(Uint128::zero(), |acc, bond| { acc + bond.amount }); - // check that the res amount matches the amount in both queues + // check that the res amount matches the amount in the pending queue ONLY // only if there are messages if res.messages.len() !=0 { match &res.messages[0].msg { @@ -271,7 +261,7 @@ mod tests { amount, &Coin { denom: "ibc/local_osmo".to_string(), - amount: failed_total_amount + pending_total_amount, + amount: pending_total_amount, } ); } @@ -280,9 +270,22 @@ mod tests { } - // check that the failed join & pending queues are emptied - prop_assert!(FAILED_JOIN_QUEUE.is_empty(&deps.storage).unwrap()); + // if BOND_QUEUE & REJOIN_QUEUE are empty FAILED_JOIN_QUEUE items are not moved to REJOIN_QUEUE + if !pending_bonds.is_empty() && !failed.bonds.is_empty() { + prop_assert!(FAILED_JOIN_QUEUE.is_empty(&deps.storage).unwrap()); + } + + // PENDING_BOND_QUEUE should be empty prop_assert!(PENDING_BOND_QUEUE.is_empty(&deps.storage).unwrap()); + + // failed bonds should be now in the REJOIN_QUEUE + let rejoin_queue: Result, StdError> = + REJOIN_QUEUE.iter(&deps.storage).unwrap().collect(); + + // only check when there's pending bonds & failed bonds + if !pending_bonds.is_empty() && !failed.bonds.is_empty() { + assert_eq!(failed.bonds, rejoin_queue.unwrap()); + } } } } diff --git a/smart-contracts/contracts/lp-strategy/src/queries.rs b/smart-contracts/contracts/lp-strategy/src/queries.rs index a5c0f5ecc..734896d36 100644 --- a/smart-contracts/contracts/lp-strategy/src/queries.rs +++ b/smart-contracts/contracts/lp-strategy/src/queries.rs @@ -22,9 +22,9 @@ use crate::{ state::{ FundPath, OngoingDeposit, Unbond, BONDING_CLAIMS, BOND_QUEUE, CHANNELS, CLAIMABLE_FUNDS, CONFIG, FAILED_JOIN_QUEUE, IBC_LOCK, ICA_CHANNEL, LP_SHARES, OSMO_LOCK, PENDING_ACK, - PENDING_BOND_QUEUE, PENDING_UNBONDING_CLAIMS, REJOIN_QUEUE, REPLIES, SHARES, - SIMULATED_JOIN_AMOUNT_IN, SIMULATED_JOIN_RESULT, START_UNBOND_QUEUE, TOTAL_VAULT_BALANCE, - TRAPS, UNBONDING_CLAIMS, UNBOND_QUEUE, + PENDING_BOND_QUEUE, PENDING_UNBONDING_CLAIMS, PENDING_UNBOND_QUEUE, REJOIN_QUEUE, REPLIES, + SHARES, SIMULATED_JOIN_AMOUNT_IN, SIMULATED_JOIN_RESULT, START_UNBOND_QUEUE, + TOTAL_VAULT_BALANCE, TRAPS, UNBONDING_CLAIMS, UNBOND_QUEUE, }, }; @@ -63,6 +63,7 @@ pub fn handle_get_queues(deps: Deps) -> StdResult { let uq: Result, StdError> = UNBOND_QUEUE.iter(deps.storage)?.collect(); let fjq: Result, StdError> = FAILED_JOIN_QUEUE.iter(deps.storage)?.collect(); let rj: Result, StdError> = REJOIN_QUEUE.iter(deps.storage)?.collect(); + let puq: Result, StdError> = PENDING_UNBOND_QUEUE.iter(deps.storage)?.collect(); Ok(GetQueuesResponse { pending_bond_queue: pbq?, bond_queue: bq?, @@ -70,6 +71,7 @@ pub fn handle_get_queues(deps: Deps) -> StdResult { unbond_queue: uq?, failed_join_queue: fjq?, rejoin_queue: rj?, + pending_unbond_queue: puq?, }) } @@ -287,6 +289,19 @@ mod tests { let _res = query(deps.as_ref(), env, q).unwrap(); } + #[test] + fn get_trapped_errors_when_empty() { + let deps = mock_dependencies(); + let env = mock_env(); + + let q = QueryMsg::TrappedErrors {}; + + let res: TrappedErrorsResponse = + from_binary(&query(deps.as_ref(), env, q).unwrap()).unwrap(); + + assert!(res.errors.is_empty()); + } + #[test] fn proper_get_claimable_funds() { let mut deps = mock_dependencies(); diff --git a/tests/e2e/Makefile b/tests/e2e/Makefile index a437a334b..3c7347b18 100644 --- a/tests/e2e/Makefile +++ b/tests/e2e/Makefile @@ -4,27 +4,30 @@ TEST_FOLDERS=$(shell find ./cases -mindepth 1 -maxdepth 1 -type d \( -name "[!_] # Run tests in the specified folders serially, if CASES is defined; otherwise, run all. test-e2e: ifdef CASES - # Loop through each folder specified in CASES and run the tests + # Loop through each folder specified in CASES and run the tests @for folder in $(filter $(CASES), $(TEST_FOLDERS)); do \ - (echo "\nRunning tests in directory: $$folder"; cd $$folder && go test ./... -v -timeout 99999s); \ + (echo "\nRunning tests in directory: $$folder"; \ + cd $$folder && go test ./... -v -timeout 99999s); \ done else - # Loop through each folder in TEST_FOLDERS and run the tests + # Loop through each folder in TEST_FOLDERS and run the tests @for folder in $(TEST_FOLDERS); do \ - (echo "\nRunning tests in directory: $$folder"; cd $$folder && go test ./... -v -timeout 99999s); \ + (echo "\nRunning tests in directory: $$folder"; \ + cd $$folder && go test ./... -v -timeout 99999s); \ done endif # Run tests in the specified folders in parallel, if CASES is defined; otherwise, run all. test-e2e-parallel: ifdef CASES - # Loop through each folder specified in CASES and run the tests in the background @for folder in $(filter $(CASES), $(TEST_FOLDERS)); do \ - (echo "\nRunning tests in directory: $$folder"; cd $$folder && go test ./... -timeout 99999s) & \ - done; wait # Wait for all background jobs to finish + echo "\nRunning tests in directory: $$folder"; \ + cd $$folder && go test ./... -timeout 99999s & \ + done; wait else - # Loop through each folder in TEST_FOLDERS and run the tests in the background @for folder in $(TEST_FOLDERS); do \ - (echo "\nRunning tests in directory: $$folder"; cd $$folder && go test ./... -timeout 99999s) & \ - done; wait # Wait for all background jobs to finish + echo "\nRunning tests in directory: $$folder"; \ + cd $$folder && go test ./... -timeout 99999s & \ + done; wait endif + diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 964b17bb0..91491145a 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -48,15 +48,15 @@ To create end-to-end tests that involve Wasm contracts, first compile the contra For Mac Silicon users: - ```bash - docker run --rm -v "$(pwd)":/code --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/workspace-optimizer-arm64:0.12.11 - ``` +```bash +docker run --rm -v "$(pwd)":/code --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/workspace-optimizer-arm64:0.12.11 +``` For other users: - ```bash - docker run --rm -v "$(pwd)":/code --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/workspace-optimizer:0.12.11 - ``` +```bash +docker run --rm -v "$(pwd)":/code --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/workspace-optimizer:0.12.11 +``` ## Testing @@ -71,6 +71,11 @@ To execute all test cases, simply run the following command: cd tests/e2e make test-e2e ``` +or its parallel variant +```bash +cd tests/e2e +make test-e2e-parallel +``` This command will iterate over all test folders found within `./tests/e2e/cases`, excluding those beginning with an underscore. For each of these folders, it will execute the `go test ./...` command. @@ -78,15 +83,15 @@ underscore. For each of these folders, it will execute the `go test ./...` comma ### Running Specific Test Cases If you want to run specific test cases instead of the entire suite, you can do so by using an environment variable named -`CASES` to specify the test case folders. For example, to run the test cases `case1`, `case2`, and `case3`, you would +`CASES` to specify the test case folders. For example, to run the test cases `case1_folder`, `case2_folder`, and `case3_folder`, you would use the following command: ```bash cd tests/e2e -CASES="case1 case2 case3" make test-e2e +CASES="./cases/case1_folder /cases/case2_folder /cases/case3_folder" make test-e2e ``` -This command will only execute the `go test ./...` command in the directories `case1`, `case2`, and `case3`. +This command will only execute the `go test ./...` command in the desired subdirectories. --- diff --git a/tests/e2e/cases/_helpers/contract.go b/tests/e2e/cases/_helpers/contract.go new file mode 100644 index 000000000..262a5c488 --- /dev/null +++ b/tests/e2e/cases/_helpers/contract.go @@ -0,0 +1,23 @@ +package helpers + +import ( + "log" + "strconv" + "strings" +) + +func ParseTrappedError(trappedErrors map[string]interface{}) (uint64, string) { + var seqError uint64 + var channelIdError string + for key := range trappedErrors { + splitKey := strings.Split(key, "-") + seqTemp, err := strconv.ParseInt(splitKey[0], 10, 64) + if err != nil { + log.Fatalf("Failed to convert seq to int64: %v", err) + } + seqError = uint64(seqTemp) + channelIdError = splitKey[1] + "-" + splitKey[2] + break + } + return seqError, channelIdError +} diff --git a/tests/e2e/cases/_utils/sample_pool1.json b/tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool1.json similarity index 63% rename from tests/e2e/cases/_utils/sample_pool1.json rename to tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool1.json index cb2776d76..e4fdf6729 100644 --- a/tests/e2e/cases/_utils/sample_pool1.json +++ b/tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool1.json @@ -1,6 +1,6 @@ { "weights": "4stake1,4uosmo", - "initial-deposit": "1746857563516stake1,32464389444717uosmo", + "initial-deposit": "2000000000000stake1,2000000000000uosmo", "swap-fee": "0.01", "exit-fee": "0.00", "future-governor": "168h" diff --git a/tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool2.json b/tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool2.json new file mode 100644 index 000000000..da9b7ab33 --- /dev/null +++ b/tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool2.json @@ -0,0 +1,7 @@ +{ + "weights": "4uosmo,4usdc", + "initial-deposit": "2000000000000uosmo,2000000000000usdc", + "swap-fee": "0.01", + "exit-fee": "0.00", + "future-governor": "168h" +} \ No newline at end of file diff --git a/tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool3.json b/tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool3.json new file mode 100644 index 000000000..e872d6561 --- /dev/null +++ b/tests/e2e/cases/_utils/pools/high_liquidity/balancer_pool3.json @@ -0,0 +1,7 @@ +{ + "weights": "4fakestake,4uosmo", + "initial-deposit": "2000000000000fakestake,2000000000000uosmo", + "swap-fee": "0.01", + "exit-fee": "0.00", + "future-governor": "168h" +} \ No newline at end of file diff --git a/tests/e2e/cases/_utils/sample_pool3.json b/tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool1.json similarity index 63% rename from tests/e2e/cases/_utils/sample_pool3.json rename to tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool1.json index bccffe7a2..dcc8f85be 100644 --- a/tests/e2e/cases/_utils/sample_pool3.json +++ b/tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool1.json @@ -1,5 +1,5 @@ { - "initial-deposit": "106055963258fakestake,56739266313uosmo", + "initial-deposit": "2000000000000stake1,2000000000000uosmo", "swap-fee": "0.01", "exit-fee": "0.00", "future-governor": "168h", diff --git a/tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool2.json b/tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool2.json new file mode 100644 index 000000000..6c320657a --- /dev/null +++ b/tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool2.json @@ -0,0 +1,7 @@ +{ + "initial-deposit": "2000000000000uosmo,2000000000000usdc", + "swap-fee": "0.01", + "exit-fee": "0.00", + "future-governor": "168h", + "scaling-factors": "1,1" +} \ No newline at end of file diff --git a/tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool3.json b/tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool3.json new file mode 100644 index 000000000..d280750dc --- /dev/null +++ b/tests/e2e/cases/_utils/pools/high_liquidity/stableswap_pool3.json @@ -0,0 +1,7 @@ +{ + "initial-deposit": "2000000000000fakestake,2000000000000uosmo", + "swap-fee": "0.01", + "exit-fee": "0.00", + "future-governor": "168h", + "scaling-factors": "1,1" +} \ No newline at end of file diff --git a/tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool1.json b/tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool1.json new file mode 100644 index 000000000..736a374e2 --- /dev/null +++ b/tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool1.json @@ -0,0 +1,7 @@ +{ + "weights": "4stake1,4uosmo", + "initial-deposit": "5000000stake1,5000000uosmo", + "swap-fee": "0.01", + "exit-fee": "0.00", + "future-governor": "168h" +} \ No newline at end of file diff --git a/tests/e2e/cases/_utils/sample_pool2.json b/tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool2.json similarity index 64% rename from tests/e2e/cases/_utils/sample_pool2.json rename to tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool2.json index aad3f1dda..d8a2c4e28 100644 --- a/tests/e2e/cases/_utils/sample_pool2.json +++ b/tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool2.json @@ -1,6 +1,6 @@ { "weights": "4uosmo,4usdc", - "initial-deposit": "1171363106031uosmo,981470256105usdc", + "initial-deposit": "5000000uosmo,5000000usdc", "swap-fee": "0.01", "exit-fee": "0.00", "future-governor": "168h" diff --git a/tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool3.json b/tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool3.json new file mode 100644 index 000000000..bbf775068 --- /dev/null +++ b/tests/e2e/cases/_utils/pools/low_liquidity/balancer_pool3.json @@ -0,0 +1,7 @@ +{ + "weights": "4fakestake,4uosmo", + "initial-deposit": "5000000fakestake,5000000uosmo", + "swap-fee": "0.01", + "exit-fee": "0.00", + "future-governor": "168h" +} \ No newline at end of file diff --git a/tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool1.json b/tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool1.json new file mode 100644 index 000000000..891f68be7 --- /dev/null +++ b/tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool1.json @@ -0,0 +1,7 @@ +{ + "initial-deposit": "2000000stake1,2000000uosmo", + "swap-fee": "0.01", + "exit-fee": "0.00", + "future-governor": "168h", + "scaling-factors": "1,1" +} \ No newline at end of file diff --git a/tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool2.json b/tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool2.json new file mode 100644 index 000000000..599746380 --- /dev/null +++ b/tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool2.json @@ -0,0 +1,7 @@ +{ + "initial-deposit": "2000000uosmo,2000000usdc", + "swap-fee": "0.01", + "exit-fee": "0.00", + "future-governor": "168h", + "scaling-factors": "1,1" +} \ No newline at end of file diff --git a/tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool3.json b/tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool3.json new file mode 100644 index 000000000..2617798d3 --- /dev/null +++ b/tests/e2e/cases/_utils/pools/low_liquidity/stableswap_pool3.json @@ -0,0 +1,7 @@ +{ + "initial-deposit": "2000000fakestake,2000000uosmo", + "swap-fee": "0.01", + "exit-fee": "0.00", + "future-governor": "168h", + "scaling-factors": "1,1" +} \ No newline at end of file diff --git a/tests/e2e/cases/osmosis_gauge/osmosis_gauge_test.go b/tests/e2e/cases/osmosis_gauge/osmosis_gauge_test.go index bd0477e2a..a985fa1ff 100644 --- a/tests/e2e/cases/osmosis_gauge/osmosis_gauge_test.go +++ b/tests/e2e/cases/osmosis_gauge/osmosis_gauge_test.go @@ -19,7 +19,7 @@ import ( ) const ( - osmosisPool1Path = "../_utils/sample_pool1.json" + osmosisPool1Path = "../_utils/pools/high_liquidity/balancer_pool1.json" userFunds int64 = int64(100_000_000_000) ibcTransferAmount int64 = int64(10_000_000_000) ) @@ -138,5 +138,5 @@ func (s *OsmosisGauge) CreatePools(ctx context.Context) { // Read the pool details from os file poolBz, err := os.ReadFile(osmosisPool1Path) s.Require().NoError(err) - s.CreatePoolsOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") } diff --git a/tests/e2e/cases/qtransfer/qtransfer_helpers_test.go b/tests/e2e/cases/qtransfer/qtransfer_helpers_test.go index 1b88587f0..f26a861f6 100644 --- a/tests/e2e/cases/qtransfer/qtransfer_helpers_test.go +++ b/tests/e2e/cases/qtransfer/qtransfer_helpers_test.go @@ -144,15 +144,15 @@ func (s *Qtransfer) CreatePools(ctx context.Context) { // Read the pool details from os file poolBz, err := os.ReadFile(osmosisPool1Path) s.Require().NoError(err) - s.CreatePoolsOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") // Read the contract from os file poolBz, err = os.ReadFile(osmosisPool2Path) s.Require().NoError(err) - s.CreatePoolsOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") // Read the contract from os file poolBz, err = os.ReadFile(osmosisPool3Path) s.Require().NoError(err) - s.CreatePoolsOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") } diff --git a/tests/e2e/cases/qtransfer/qtransfer_test.go b/tests/e2e/cases/qtransfer/qtransfer_test.go index e388c814c..2a4251439 100644 --- a/tests/e2e/cases/qtransfer/qtransfer_test.go +++ b/tests/e2e/cases/qtransfer/qtransfer_test.go @@ -20,9 +20,9 @@ const ( lpStrategyContractPath = "../../../../smart-contracts/artifacts/lp_strategy-aarch64.wasm" basicVaultStrategyContractPath = "../../../../smart-contracts/artifacts/basic_vault-aarch64.wasm" vaultRewardsContractPath = "../../../../smart-contracts/artifacts/vault_rewards-aarch64.wasm" - osmosisPool1Path = "../_utils/sample_pool1.json" - osmosisPool2Path = "../_utils/sample_pool2.json" - osmosisPool3Path = "../_utils/sample_pool3.json" + osmosisPool1Path = "../_utils/pools/high_liquidity/balancer_pool1.json" + osmosisPool2Path = "../_utils/pools/high_liquidity/balancer_pool2.json" + osmosisPool3Path = "../_utils/pools/high_liquidity/balancer_pool3.json" QTUserFundAmount int64 = 1_003_500 QTTransferAmount int64 = 1_000_000 @@ -138,6 +138,7 @@ func (s *Qtransfer) SetupSuite() { "symbol": "ORN", "min_withdrawal": "1", "name": "ORION", + "deposit_denom": s.OsmosisDenomInQuasar, "primitives": []map[string]any{ { "address": s.LpStrategyContractAddress1, diff --git a/tests/e2e/cases/wasmd/wasmd_helpers_test.go b/tests/e2e/cases/wasmd_deposit/wasmd_helpers_test.go similarity index 93% rename from tests/e2e/cases/wasmd/wasmd_helpers_test.go rename to tests/e2e/cases/wasmd_deposit/wasmd_helpers_test.go index dc05ee1d8..97fb4d1c0 100644 --- a/tests/e2e/cases/wasmd/wasmd_helpers_test.go +++ b/tests/e2e/cases/wasmd_deposit/wasmd_helpers_test.go @@ -1,4 +1,4 @@ -package wasmd +package wasmd_deposit import ( "context" @@ -144,15 +144,15 @@ func (s *WasmdTestSuite) CreatePools(ctx context.Context) { // Read the pool details from os file poolBz, err := os.ReadFile(osmosisPool1Path) s.Require().NoError(err) - s.CreatePoolsOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") // Read the contract from os file poolBz, err = os.ReadFile(osmosisPool2Path) s.Require().NoError(err) - s.CreatePoolsOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") // Read the contract from os file poolBz, err = os.ReadFile(osmosisPool3Path) s.Require().NoError(err) - s.CreateStableswapPoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") } diff --git a/tests/e2e/cases/wasmd/wasmd_strategy_lp_deposit_test.go b/tests/e2e/cases/wasmd_deposit/wasmd_strategy_lp_deposit_test.go similarity index 97% rename from tests/e2e/cases/wasmd/wasmd_strategy_lp_deposit_test.go rename to tests/e2e/cases/wasmd_deposit/wasmd_strategy_lp_deposit_test.go index c13a6d0f1..09d5bf289 100644 --- a/tests/e2e/cases/wasmd/wasmd_strategy_lp_deposit_test.go +++ b/tests/e2e/cases/wasmd_deposit/wasmd_strategy_lp_deposit_test.go @@ -1,4 +1,4 @@ -package wasmd +package wasmd_deposit import ( "context" @@ -22,9 +22,9 @@ const ( lpStrategyContractPath = "../../../../smart-contracts/artifacts/lp_strategy-aarch64.wasm" basicVaultStrategyContractPath = "../../../../smart-contracts/artifacts/basic_vault-aarch64.wasm" vaultRewardsContractPath = "../../../../smart-contracts/artifacts/vault_rewards-aarch64.wasm" - osmosisPool1Path = "../_utils/sample_pool1.json" - osmosisPool2Path = "../_utils/sample_pool2.json" - osmosisPool3Path = "../_utils/sample_pool3.json" + osmosisPool1Path = "../_utils/pools/high_liquidity/balancer_pool1.json" + osmosisPool2Path = "../_utils/pools/high_liquidity/balancer_pool2.json" + osmosisPool3Path = "../_utils/pools/high_liquidity/balancer_pool3.json" ) var ( @@ -133,7 +133,7 @@ func (s *WasmdTestSuite) SetupSuite() { "symbol": "ORN", "min_withdrawal": "1", "name": "ORION", - "deposit_denom": "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", + "deposit_denom": s.OsmosisDenomInQuasar, "primitives": []map[string]any{ { "address": s.LpStrategyContractAddress1, @@ -227,14 +227,14 @@ func (s *WasmdTestSuite) TestLpStrategyContract_SuccessfulDeposit() { Action: "bond", BondAmount: sdk.NewCoins(sdk.NewInt64Coin("ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", 10000000)), expectedShares: 9999999, - expectedDeviation: 0.01, + expectedDeviation: 0.1, }, { Account: *accBondTest1, Action: "bond", BondAmount: sdk.NewCoins(sdk.NewInt64Coin("ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", 1000000)), expectedShares: 1015176, - expectedDeviation: 0.01, + expectedDeviation: 0.1, }, { Account: *accBondTest0, @@ -261,7 +261,7 @@ func (s *WasmdTestSuite) TestLpStrategyContract_SuccessfulDeposit() { Action: "bond", BondAmount: sdk.NewCoins(sdk.NewInt64Coin("ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", 1000000)), expectedShares: 1015176, - expectedDeviation: 0.01, + expectedDeviation: 0.1, }, } diff --git a/tests/e2e/cases/wasmd_retry/wasmd_helpers_test.go b/tests/e2e/cases/wasmd_retry/wasmd_helpers_test.go new file mode 100644 index 000000000..86f369652 --- /dev/null +++ b/tests/e2e/cases/wasmd_retry/wasmd_helpers_test.go @@ -0,0 +1,166 @@ +package wasmd_deposit + +import ( + "context" + "fmt" + testsuite "github.com/quasarlabs/quasarnode/tests/e2e/suite" + "os" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/strangelove-ventures/interchaintest/v4/ibc" +) + +// deployPrimitives stores the contract, initiates it and returns the contract address. +func (s *WasmdTestSuite) deployPrimitives(ctx context.Context, acc *ibc.Wallet, filePath, label string, initArgs1, initArgs2, initArgs3 any) (string, string, string) { + accAddress := acc.Bech32Address(s.Quasar().Config().Bech32Prefix) + + // Read the contract from os file + contract, err := os.ReadFile(filePath) + s.Require().NoError(err) + + // Store the contract in chain + codeID := s.StoreContractCode(ctx, s.Quasar(), acc.KeyName, contract) + s.PrimitiveStoreID = codeID + + // instantiate the contracts + res := s.InstantiateContract(ctx, s.Quasar(), acc.KeyName, codeID, label, accAddress, sdk.NewCoins(), initArgs1) + s.Require().NotEmpty(res.Address) + lpStrategyContractAddress1 := res.Address + + // create channels for all the instantiated contracts address 1 + s.CreateChannel( + ctx, + testsuite.Quasar2OsmosisPath, + fmt.Sprintf("wasm.%s", lpStrategyContractAddress1), + "icqhost", + ibc.Unordered, + "icq-1", + ) + + s.CreateChannel( + ctx, + testsuite.Quasar2OsmosisPath, + fmt.Sprintf("wasm.%s", lpStrategyContractAddress1), + "icahost", + ibc.Ordered, + fmt.Sprintf( + `{"version":"ics27-1","encoding":"proto3","tx_type":"sdk_multi_msg","controller_connection_id":"%s","host_connection_id":"%s"}`, + s.Quasar2OsmosisConn.Id, + s.Quasar2OsmosisConn.Counterparty.ConnectionId, + ), + ) + + res = s.InstantiateContract(ctx, s.Quasar(), acc.KeyName, codeID, label, accAddress, sdk.NewCoins(), initArgs2) + s.Require().NotEmpty(res.Address) + lpStrategyContractAddress2 := res.Address + + // create channels for all the instantiated contracts address 2 + s.CreateChannel( + ctx, + testsuite.Quasar2OsmosisPath, + fmt.Sprintf("wasm.%s", lpStrategyContractAddress2), + "icqhost", + ibc.Unordered, + "icq-1", + ) + + s.CreateChannel( + ctx, + testsuite.Quasar2OsmosisPath, + fmt.Sprintf("wasm.%s", lpStrategyContractAddress2), + "icahost", + ibc.Ordered, + fmt.Sprintf( + `{"version":"ics27-1","encoding":"proto3","tx_type":"sdk_multi_msg","controller_connection_id":"%s","host_connection_id":"%s"}`, + s.Quasar2OsmosisConn.Id, + s.Quasar2OsmosisConn.Counterparty.ConnectionId, + ), + ) + + res = s.InstantiateContract(ctx, s.Quasar(), acc.KeyName, codeID, label, accAddress, sdk.NewCoins(), initArgs3) + s.Require().NotEmpty(res.Address) + lpStrategyContractAddress3 := res.Address + + // create channels for all the instantiated contracts address 3 + s.CreateChannel( + ctx, + testsuite.Quasar2OsmosisPath, + fmt.Sprintf("wasm.%s", lpStrategyContractAddress3), + "icqhost", + ibc.Unordered, + "icq-1", + ) + + s.CreateChannel( + ctx, + testsuite.Quasar2OsmosisPath, + fmt.Sprintf("wasm.%s", lpStrategyContractAddress3), + "icahost", + ibc.Ordered, + fmt.Sprintf( + `{"version":"ics27-1","encoding":"proto3","tx_type":"sdk_multi_msg","controller_connection_id":"%s","host_connection_id":"%s"}`, + s.Quasar2OsmosisConn.Id, + s.Quasar2OsmosisConn.Counterparty.ConnectionId, + ), + ) + return lpStrategyContractAddress1, lpStrategyContractAddress2, lpStrategyContractAddress3 +} + +// deployRewardsContract stores the contract +func (s *WasmdTestSuite) deployRewardsContract(ctx context.Context, acc *ibc.Wallet, filePath string) { + // Read the contract from os file + contract, err := os.ReadFile(filePath) + s.Require().NoError(err) + + // Store the contract in chain + codeID := s.StoreContractCode(ctx, s.Quasar(), acc.KeyName, contract) + s.RewardsStoreID = codeID +} + +// deployVault stores the contract, initiates it and returns the contract address. +func (s *WasmdTestSuite) deployVault(ctx context.Context, acc *ibc.Wallet, filePath, label string, initArgs any) string { + accAddress := acc.Bech32Address(s.Quasar().Config().Bech32Prefix) + + // Read the contract from os file + contract, err := os.ReadFile(filePath) + s.Require().NoError(err) + + // Store the contract in chain + codeID := s.StoreContractCode(ctx, s.Quasar(), acc.KeyName, contract) + s.VaultStoreID = codeID + + res := s.InstantiateContract(ctx, s.Quasar(), acc.KeyName, codeID, label, accAddress, sdk.NewCoins(), initArgs) + s.Require().NotEmpty(res.Address) + + return res.Address +} + +func (s *WasmdTestSuite) setDepositorForContracts(ctx context.Context, acc *ibc.Wallet, initArgs any, lpAddresses []string) { + s.SetDepositors(ctx, s.Quasar(), lpAddresses[0], acc.KeyName, initArgs) + s.SetDepositors(ctx, s.Quasar(), lpAddresses[1], acc.KeyName, initArgs) + s.SetDepositors(ctx, s.Quasar(), lpAddresses[2], acc.KeyName, initArgs) +} + +func (s *WasmdTestSuite) CreatePools(ctx context.Context) { + // Read the pool details from os file + poolBz, err := os.ReadFile(osmosisPool1Path) + s.Require().NoError(err) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") + + // Read the contract from os file + poolBz, err = os.ReadFile(osmosisPool2Path) + s.Require().NoError(err) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") + + // Read the contract from os file + poolBz, err = os.ReadFile(osmosisPool3Path) + s.Require().NoError(err) + s.CreatePoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolBz, "") +} + +func (s *WasmdTestSuite) JoinPools(ctx context.Context, poolIds []string, maxAmountsIn []string, sharesAmountOut []string) { + // TODO: require len(allTheArgs) is the same + for i, _ := range poolIds { + s.JoinPoolOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, poolIds[i], maxAmountsIn[i], sharesAmountOut[i]) + } +} diff --git a/tests/e2e/cases/wasmd_retry/wasmd_strategy_lp_retry_test.go b/tests/e2e/cases/wasmd_retry/wasmd_strategy_lp_retry_test.go new file mode 100644 index 000000000..beff7607a --- /dev/null +++ b/tests/e2e/cases/wasmd_retry/wasmd_strategy_lp_retry_test.go @@ -0,0 +1,567 @@ +package wasmd_deposit + +import ( + "context" + "encoding/json" + "github.com/quasarlabs/quasarnode/tests/e2e/cases/_helpers" + "strconv" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + connectiontypes "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" + testsuite "github.com/quasarlabs/quasarnode/tests/e2e/suite" + "github.com/strangelove-ventures/interchaintest/v4/ibc" + "github.com/strangelove-ventures/interchaintest/v4/testutil" + "github.com/stretchr/testify/suite" +) + +const ( + StartingTokenAmount int64 = 100_000_000_000 + BondAmount int64 = 10_000_000 + SharesAmount int64 = 10_000_000 + lpStrategyContractPath = "../../../../smart-contracts/artifacts/lp_strategy-aarch64.wasm" + basicVaultStrategyContractPath = "../../../../smart-contracts/artifacts/basic_vault-aarch64.wasm" + vaultRewardsContractPath = "../../../../smart-contracts/artifacts/vault_rewards-aarch64.wasm" + osmosisPool1Path = "../_utils/pools/low_liquidity/balancer_pool1.json" + osmosisPool2Path = "../_utils/pools/low_liquidity/balancer_pool2.json" + osmosisPool3Path = "../_utils/pools/low_liquidity/balancer_pool3.json" +) + +var ( + // Join + init1 = map[string]any{ + "lock_period": 6, "pool_id": 1, "pool_denom": "gamm/pool/1", "base_denom": "uosmo", + "local_denom": "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", "quote_denom": "stake1", + "return_source_channel": "channel-0", "transfer_channel": "channel-0", "expected_connection": "connection-0", + } + init2 = map[string]any{ + "lock_period": 6, "pool_id": 2, "pool_denom": "gamm/pool/2", "base_denom": "uosmo", + "local_denom": "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", "quote_denom": "usdc", + "return_source_channel": "channel-0", "transfer_channel": "channel-0", "expected_connection": "connection-0", + } + init3 = map[string]any{ + "lock_period": 6, "pool_id": 3, "pool_denom": "gamm/pool/3", "base_denom": "uosmo", + "local_denom": "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", "quote_denom": "fakestake", + "return_source_channel": "channel-0", "transfer_channel": "channel-0", "expected_connection": "connection-0", + } + // Exit + init4 = map[string]any{ + "lock_period": 6, "pool_id": 4, "pool_denom": "gamm/pool/4", "base_denom": "uosmo", + "local_denom": "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", "quote_denom": "stake1", + "return_source_channel": "channel-0", "transfer_channel": "channel-0", "expected_connection": "connection-0", + } + init5 = map[string]any{ + "lock_period": 6, "pool_id": 5, "pool_denom": "gamm/pool/5", "base_denom": "uosmo", + "local_denom": "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", "quote_denom": "usdc", + "return_source_channel": "channel-0", "transfer_channel": "channel-0", "expected_connection": "connection-0", + } + init6 = map[string]any{ + "lock_period": 6, "pool_id": 6, "pool_denom": "gamm/pool/6", "base_denom": "uosmo", + "local_denom": "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", "quote_denom": "fakestake", + "return_source_channel": "channel-0", "transfer_channel": "channel-0", "expected_connection": "connection-0", + } +) + +func TestWasmdTestSuite(t *testing.T) { + if testing.Short() { + t.Skip() + } + + b := testsuite.NewE2ETestSuiteBuilder(t) + b.UseOsmosis() + b.Link(testsuite.Quasar2OsmosisPath) + b.AutomatedRelay() + + s := &WasmdTestSuite{ + E2EBuilder: b, + E2ETestSuite: b.Build(), + } + suite.Run(t, s) +} + +type WasmdTestSuite struct { + E2EBuilder *testsuite.E2ETestSuiteBuilder + + *testsuite.E2ETestSuite + + Quasar2OsmosisConn *connectiontypes.IdentifiedConnection + Osmosis2QuasarConn *connectiontypes.IdentifiedConnection + + Quasar2OsmosisTransferChan *channeltypes.IdentifiedChannel + Osmosis2QuasarTransferChan *channeltypes.IdentifiedChannel + + OsmosisDenomInQuasar string + QuasarDenomInOsmosis string + + ContractsDeploymentWallet *ibc.Wallet + + RewardsStoreID uint64 + PrimitiveStoreID uint64 + VaultStoreID uint64 +} + +func (s *WasmdTestSuite) SetupSuite() { + t := s.T() + ctx := context.Background() + + // Wait for IBC connections to be established + t.Log("Wait for chains to settle up the ibc connection states") + err := testutil.WaitForBlocks(ctx, 5, s.Quasar(), s.Osmosis()) + s.Require().NoError(err) + + // Find out connections between each pair of chains + s.Quasar2OsmosisConn = s.GetConnectionsByPath(ctx, testsuite.Quasar2OsmosisPath)[0] + s.Osmosis2QuasarConn = s.GetConnectionsByPath(ctx, testsuite.Quasar2OsmosisPath)[0] + + // Find out transfer channel between each pair of chains + s.Quasar2OsmosisTransferChan = s.QueryConnectionChannels(ctx, s.Quasar(), s.Quasar2OsmosisConn.Id)[0] + s.Osmosis2QuasarTransferChan = s.QueryConnectionChannels(ctx, s.Osmosis(), s.Osmosis2QuasarConn.Id)[0] + + // Generate the ibc denom of native tokens in other chains + s.OsmosisDenomInQuasar = helpers.IbcDenomFromChannel(s.Quasar2OsmosisTransferChan, s.Osmosis().Config().Denom) + s.QuasarDenomInOsmosis = helpers.IbcDenomFromChannelCounterparty(s.Quasar2OsmosisTransferChan, s.Quasar().Config().Denom) + + // Setup an account in quasar chain for contract deployment + s.ContractsDeploymentWallet = s.CreateUserAndFund(ctx, s.Quasar(), StartingTokenAmount) + + // Create Pools twice in order to create them in a range from poolId 1 to 6 for both test cases as Join and Exit + s.CreatePools(ctx) + s.CreatePools(ctx) +} + +func (s *WasmdTestSuite) TestLpStrategyContract_JoinPoolRetry() { + t := s.T() + ctx := context.Background() + basicVaultAddress, lpAddresses := s.deployContracts(ctx, []map[string]any{init1, init2, init3}) + + // create user and check his balance + acc := s.createUserAndCheckBalances(ctx) + + t.Log("Execute first bond transaction, this should fail due to slippage as we bond 10/3 OSMO on 2denom:2denom assets pools") + s.executeBond(ctx, acc, basicVaultAddress) + t.Log("Execute sandwich attack before ICA/ICQ to finish the process") + s.executeSandwichAttackJoin(ctx) + t.Log("Execute first clear cache to perform the joinPool on the osmosis side") + s.executeClearCache(ctx, basicVaultAddress) + + t.Log("Check that the user shares balance is still 0 as the joinPool didn't happen due to slippage on the osmosis side") + balance := s.getUserSharesBalance(ctx, acc, basicVaultAddress) + s.Require().True(int64(0) == balance) + + t.Log("Get the counterparty ICA osmo1 addresses for each one of the primitive and check their uosmo balances after executing bond that failed") + icaAddresses := s.getPrimitiveIcaAddresses(ctx, []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}) + + t.Log("Check uOSMO balance of the primitives looking for BOND_AMOUNT/3 on each one of them") + balanceIca1, err := s.Osmosis().GetBalance(ctx, icaAddresses[0], "uosmo") + s.Require().NoError(err) + s.Require().Equal(BondAmount/3, balanceIca1) + balanceIca2, err := s.Osmosis().GetBalance(ctx, icaAddresses[1], "uosmo") + s.Require().NoError(err) + s.Require().Equal(BondAmount/3, balanceIca2) + balanceIca3, err := s.Osmosis().GetBalance(ctx, icaAddresses[2], "uosmo") + s.Require().NoError(err) + s.Require().Equal(BondAmount/3, balanceIca3) + + t.Log("Query trapped errors for each one of the primitives") + trappedErrors := s.getTrappedErrors(ctx, []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}) + + t.Log("Parsing trapped errors to obtain seq number and channel id and checking length of each is 1") + seqError1, channelIdError1 := helpers.ParseTrappedError(trappedErrors[0]) + seqError2, channelIdError2 := helpers.ParseTrappedError(trappedErrors[1]) + seqError3, channelIdError3 := helpers.ParseTrappedError(trappedErrors[2]) + s.Require().Equal(1, len(trappedErrors[0])) + s.Require().Equal(1, len(trappedErrors[1])) + s.Require().Equal(1, len(trappedErrors[2])) + + t.Log("Execute retry endpoints against each one of the primitives to enqueue previously failed join pools") + s.executeRetry( + ctx, + acc, + []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}, + []uint64{seqError1, seqError2, seqError3}, + []string{channelIdError1, channelIdError2, channelIdError3}, + ) + + t.Log("Query again trapped errors for each one of the primitives") + trappedErrorsAfter := s.getTrappedErrors(ctx, []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}) + s.Require().Equal(0, len(trappedErrorsAfter[0])) + s.Require().Equal(0, len(trappedErrorsAfter[1])) + s.Require().Equal(0, len(trappedErrorsAfter[2])) + + t.Log("Execute second bond transaction, this should work and also trigger the join_pool we enqueued previously via retry endpoint") + s.executeBond(ctx, acc, basicVaultAddress) + t.Log("Execute third clear cache to perform the joinPool on the osmosis side") + s.executeClearCache(ctx, basicVaultAddress) + + t.Log("Query again trapped errors for each one of the primitives") + trappedErrorsAfterSecondBond := s.getTrappedErrors(ctx, []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}) + s.Require().Equal(0, len(trappedErrorsAfterSecondBond[0])) + s.Require().Equal(0, len(trappedErrorsAfterSecondBond[1])) + s.Require().Equal(0, len(trappedErrorsAfterSecondBond[2])) + + t.Log("Check that the user shares balance is ~20 as both join pools should have worked") + balanceAfter := s.getUserSharesBalance(ctx, acc, basicVaultAddress) + s.Require().Equal(BondAmount*2-1-1, balanceAfter) + + t.Log("Check uOSMO balance of the primitives looking for ~0 on each one of them as they should be emptied") + balanceIca1After, err := s.Osmosis().GetBalance(ctx, icaAddresses[0], "uosmo") + s.Require().NoError(err) + s.Require().Equal(int64(0), balanceIca1After) + balanceIca2After, err := s.Osmosis().GetBalance(ctx, icaAddresses[1], "uosmo") + s.Require().NoError(err) + s.Require().Equal(int64(0), balanceIca2After) + balanceIca3After, err := s.Osmosis().GetBalance(ctx, icaAddresses[2], "uosmo") + s.Require().NoError(err) + s.Require().Equal(int64(0), balanceIca3After) +} + +func (s *WasmdTestSuite) TestLpStrategyContract_ExitPoolRetry() { + t := s.T() + ctx := context.Background() + basicVaultAddress, lpAddresses := s.deployContracts(ctx, []map[string]any{init4, init5, init6}) + + acc := s.createUserAndCheckBalances(ctx) + + t.Log("Execute first bond transaction, this should work") + s.executeBond(ctx, acc, basicVaultAddress) + + t.Log("Execute first clear cache to perform the joinPool on the osmosis side") + s.executeClearCache(ctx, basicVaultAddress) + + t.Log("Check that the user shares balance is ~10 as the joinPool should have worked") + balance := s.getUserSharesBalance(ctx, acc, basicVaultAddress) + s.Require().True(int64(9999999) == balance) + + t.Log("Get ICA addresses for each one of the primitive") + icaAddresses := s.getPrimitiveIcaAddresses(ctx, []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}) + + t.Log("uosmo balance of the primitives should be 0") + balanceIca1, err := s.Osmosis().GetBalance(ctx, icaAddresses[0], "uosmo") + s.Require().NoError(err) + s.Require().Equal(int64(0), balanceIca1) + balanceIca2, err := s.Osmosis().GetBalance(ctx, icaAddresses[1], "uosmo") + s.Require().NoError(err) + s.Require().Equal(int64(0), balanceIca2) + balanceIca3, err := s.Osmosis().GetBalance(ctx, icaAddresses[2], "uosmo") + s.Require().NoError(err) + s.Require().Equal(int64(0), balanceIca3) + + t.Log("Query trapped errors for each primitive & check length should be 0") + trappedErrors := s.getTrappedErrors(ctx, []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}) + s.Require().Equal(0, len(trappedErrors[0])) + s.Require().Equal(0, len(trappedErrors[1])) + s.Require().Equal(0, len(trappedErrors[2])) + + t.Log("Execute unbond, this should work") + s.executeUnbond(ctx, acc, basicVaultAddress) + + t.Log("Execute second clear cache to perform the begin unlocking on osmosis") + s.executeClearCache(ctx, basicVaultAddress) + + t.Log("Execute claim before ICA/ICQ. Claim should fail due to slippage") + s.executeClaim(ctx, acc, basicVaultAddress) + + t.Log("Execute third clear cache") + s.executeClearCache(ctx, basicVaultAddress) + + t.Log("Query trapped errors for each one of the primitives") + trappedErrorsAfterClaim := s.getTrappedErrors(ctx, []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}) + + t.Log("Parsing trapped errors to obtain seq number and channel id and checking length of each is 1") + seqError1Claim, channelIdError1Claim := helpers.ParseTrappedError(trappedErrorsAfterClaim[0]) + seqError2Claim, channelIdError2Claim := helpers.ParseTrappedError(trappedErrorsAfterClaim[1]) + seqError3Claim, channelIdError3Claim := helpers.ParseTrappedError(trappedErrorsAfterClaim[2]) + s.Require().Equal(1, len(trappedErrorsAfterClaim[0])) + s.Require().Equal(1, len(trappedErrorsAfterClaim[1])) + s.Require().Equal(1, len(trappedErrorsAfterClaim[2])) + + t.Log("Execute retry endpoints against all primitives") + s.executeRetry( + ctx, + acc, + []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}, + []uint64{seqError1Claim, seqError2Claim, seqError3Claim}, + []string{channelIdError1Claim, channelIdError2Claim, channelIdError3Claim}, + ) + + t.Log("Query again trapped errors for each one of the primitives") + trappedErrorsAfter := s.getTrappedErrors(ctx, []string{lpAddresses[0], lpAddresses[1], lpAddresses[2]}) + s.Require().Equal(0, len(trappedErrorsAfter[0])) + s.Require().Equal(0, len(trappedErrorsAfter[1])) + s.Require().Equal(0, len(trappedErrorsAfter[2])) + + t.Log("Fund the Osmosis pools to increase pool assets amount and reduce slippage for next retry") + // Preparing array fo payloads to joinPools, those are magic numbers based on the test's values so any change to initial setup will cause a fail here + poolIds := []string{"4", "5", "6"} + maxAmountsIn := []string{"3876858349171stake1,6461430323493uosmo", "6461430323493uosmo,3876858349171usdc", "3876858349171fakestake,6461430323493uosmo"} + sharesAmountOut := []string{"99999900000000000000000000", "99999900000000000000000000", "99999900000000000000000000"} + s.JoinPools(ctx, poolIds, maxAmountsIn, sharesAmountOut) + + t.Log("Execute fourth clear cache to perform the exit pool on osmosis") + s.executeClearCache(ctx, basicVaultAddress) + + t.Log("Check that the user shares balance is ~5 shares") + balanceAfter := s.getUserSharesBalance(ctx, acc, basicVaultAddress) + s.Require().Equal(BondAmount/2-1, balanceAfter) + + t.Log("Check uOSMO balance of the primitives looking for ~0 on each one of them as they should be emptied") + balanceIca1After, err := s.Osmosis().GetBalance(ctx, icaAddresses[0], "uosmo") + s.Require().NoError(err) + s.Require().Equal(int64(0), balanceIca1After) + balanceIca2After, err := s.Osmosis().GetBalance(ctx, icaAddresses[1], "uosmo") + s.Require().NoError(err) + s.Require().Equal(int64(0), balanceIca2After) + balanceIca3After, err := s.Osmosis().GetBalance(ctx, icaAddresses[2], "uosmo") + s.Require().NoError(err) + s.Require().Equal(int64(0), balanceIca3After) + + t.Log("Check uOSMO balance of the primitives looking for ~0 on each one of them as they should be emptied") + balanceVault, err := s.Quasar().GetBalance(ctx, basicVaultAddress, s.OsmosisDenomInQuasar) + s.Require().NoError(err) + s.Require().Equal(int64(1), balanceVault) + + t.Log("Check uOSMO balance of the user on their wallet. Should be greater than 15") + userBalance, err := s.Quasar().GetBalance(ctx, acc.Bech32Address(s.Quasar().Config().Bech32Prefix), s.OsmosisDenomInQuasar) + s.Require().NoError(err) + s.Require().True(userBalance > int64(14_999_999)) +} + +func (s *WasmdTestSuite) deployContracts(ctx context.Context, inits []map[string]any) (string, []string) { + t := s.T() + + t.Log("Deploy the lp strategy contract") + lpAddress1, lpAddress2, lpAddress3 := s.deployPrimitives(ctx, s.ContractsDeploymentWallet, lpStrategyContractPath, "lp_strategy_test", inits[0], inits[1], inits[2]) + + t.Log("Deploy reward contract") + s.deployRewardsContract(ctx, s.ContractsDeploymentWallet, vaultRewardsContractPath) + + t.Log("Deploy basic vault contract") + basicVaultAddress := s.deployVault(ctx, s.ContractsDeploymentWallet, basicVaultStrategyContractPath, "basic_vault", + map[string]any{ + "total_cap": "200000000000", + "thesis": "e2e", + "vault_rewards_code_id": s.RewardsStoreID, + "reward_token": map[string]any{"native": "uqsr"}, + "reward_distribution_schedules": []string{}, + "decimals": 6, + "symbol": "ORN", + "min_withdrawal": "1", + "name": "ORION", + "deposit_denom": s.OsmosisDenomInQuasar, + "primitives": []map[string]any{ + { + "address": lpAddress1, + "weight": "0.333333333333", + "init": map[string]any{ + "l_p": inits[0], + }, + }, + { + "address": lpAddress2, + "weight": "0.333333333333", + "init": map[string]any{ + "l_p": inits[1], + }, + }, + { + "address": lpAddress3, + "weight": "0.333333333333", + "init": map[string]any{ + "l_p": inits[2], + }, + }, + }, + }) + + t.Log("Set depositors for all the primitives") + s.setDepositorForContracts(ctx, s.ContractsDeploymentWallet, + map[string]any{ + "set_depositor": map[string]any{ + "depositor": basicVaultAddress, + }, + }, + []string{lpAddress1, lpAddress2, lpAddress3}, + ) + + return basicVaultAddress, []string{lpAddress1, lpAddress2, lpAddress3} +} + +func (s *WasmdTestSuite) createUserAndCheckBalances(ctx context.Context) *ibc.Wallet { + t := s.T() + + t.Log("Create testing account on Quasar chain with some QSR tokens for fees") + acc := s.CreateUserAndFund(ctx, s.Quasar(), 10_000_000) // unused qsr, just for tx fees + + t.Log("Fund testing account with uosmo via IBC transfer from Osmosis chain Treasury account") + walletAmount0 := ibc.WalletAmount{Address: acc.Bech32Address(s.Quasar().Config().Bech32Prefix), Denom: s.Osmosis().Config().Denom, Amount: BondAmount * 2} + transfer, err := s.Osmosis().SendIBCTransfer(ctx, s.Osmosis2QuasarTransferChan.ChannelId, s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, walletAmount0, ibc.TransferOptions{}) + s.Require().NoError(err) + s.Require().NoError(transfer.Validate()) + + t.Log("Wait for packet transfer and the ibc transfer to occur") + err = testutil.WaitForBlocks(ctx, 5, s.Quasar(), s.Osmosis()) + s.Require().NoError(err) + + t.Log("Check tester accounts uosmo balance after executing IBC transfer") + balanceTester0, err := s.Quasar().GetBalance(ctx, acc.Bech32Address(s.Quasar().Config().Bech32Prefix), s.OsmosisDenomInQuasar) + s.Require().NoError(err) + s.Require().Equal(BondAmount*2, balanceTester0) + + return acc +} + +func (s *WasmdTestSuite) executeBond(ctx context.Context, acc *ibc.Wallet, basicVaultAddress string) { + t := s.T() + + s.ExecuteContract( + ctx, + s.Quasar(), + acc.KeyName, + basicVaultAddress, + sdk.NewCoins(sdk.NewInt64Coin(s.OsmosisDenomInQuasar, BondAmount)), + map[string]any{"bond": map[string]any{}}, + nil, + ) + + t.Log("Wait 3 blocks on quasar and osmosis to settle up ICA packet transfer and the IBC transfer (bond)") + err := testutil.WaitForBlocks(ctx, 3, s.Quasar(), s.Osmosis()) + s.Require().NoError(err) +} + +func (s *WasmdTestSuite) executeUnbond(ctx context.Context, acc *ibc.Wallet, basicVaultAddress string) { + t := s.T() + + s.ExecuteContract( + ctx, + s.Quasar(), + acc.KeyName, + basicVaultAddress, + sdk.NewCoins(), + map[string]any{"unbond": map[string]any{"amount": "5000000"}}, + nil, + ) + + t.Log("Wait 5 blocks on quasar and osmosis to settle up ICA packet unbond and the IBC transfer (unbond)") + err := testutil.WaitForBlocks(ctx, 5, s.Quasar(), s.Osmosis()) + s.Require().NoError(err) +} + +func (s *WasmdTestSuite) executeClaim(ctx context.Context, acc *ibc.Wallet, basicVaultAddress string) { + t := s.T() + + s.ExecuteContract( + ctx, + s.Quasar(), + acc.KeyName, + basicVaultAddress, + sdk.NewCoins(), + map[string]any{"claim": map[string]any{}}, + nil, + ) + t.Log("Wait 3 block on quasar and osmosis to settle up ICA packet claim and the IBC transfer (claim)") + err := testutil.WaitForBlocks(ctx, 3, s.Quasar(), s.Osmosis()) + s.Require().NoError(err) +} + +func (s *WasmdTestSuite) executeClearCache(ctx context.Context, basicVaultAddress string) { + t := s.T() + + s.ExecuteContract( + ctx, + s.Quasar(), + s.ContractsDeploymentWallet.KeyName, + basicVaultAddress, + sdk.Coins{}, + map[string]any{"clear_cache": map[string]any{}}, + nil, + ) + + t.Log("Wait for quasar and osmosis to settle up ICA packet transfer and the IBC transfer (clear_cache)") + err := testutil.WaitForBlocks(ctx, 15, s.Quasar(), s.Osmosis()) + s.Require().NoError(err) +} + +func (s *WasmdTestSuite) executeSandwichAttackJoin(ctx context.Context) { + // Sandwich-attack as we know in this test how we are going to swap, we clone the tx and we execute it before the ICQ/ICA is doing the job simulating a front-run sandwich attack + s.SwapTokenOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, "3333333uosmo", "1", "stake1", "1") + s.SwapTokenOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, "3333333uosmo", "1", "usdc", "2") + s.SwapTokenOnOsmosis(ctx, s.Osmosis(), s.E2EBuilder.OsmosisAccounts.Treasury.KeyName, "3333333uosmo", "1", "fakestake", "3") +} + +func (s *WasmdTestSuite) executeRetry(ctx context.Context, acc *ibc.Wallet, lpAddresses []string, seqs []uint64, chans []string) { + for i := range seqs { + s.ExecuteContract( + ctx, + s.Quasar(), + acc.KeyName, + lpAddresses[i], + sdk.NewCoins(), // empty amount + map[string]any{"retry": map[string]any{ + "seq": seqs[i], + "channel": chans[i], + }}, + nil, + ) + } +} + +func (s *WasmdTestSuite) getUserSharesBalance(ctx context.Context, acc *ibc.Wallet, basicVaultAddress string) int64 { + var data testsuite.ContractBalanceData + balanceBytes := s.ExecuteContractQuery( + ctx, + s.Quasar(), + basicVaultAddress, + map[string]any{ + "balance": map[string]any{ + "address": acc.Bech32Address(s.Quasar().Config().Bech32Prefix), + }, + }, + ) + err := json.Unmarshal(balanceBytes, &data) + s.Require().NoError(err) + balance, err := strconv.ParseInt(data.Data.Balance, 10, 64) + s.Require().NoError(err) + + return balance +} + +func (s *WasmdTestSuite) getPrimitiveIcaAddresses(ctx context.Context, lpAddresses []string) []string { + var icaAddresses []string + for _, lpAddress := range lpAddresses { + var icaAddress testsuite.ContractIcaAddressData + icaAddress1Bytes := s.ExecuteContractQuery( + ctx, + s.Quasar(), + lpAddress, + map[string]any{ + "ica_address": map[string]any{}, + }, + ) + err := json.Unmarshal(icaAddress1Bytes, &icaAddress) + s.Require().NoError(err) + icaAddresses = append(icaAddresses, icaAddress.Data.Address) + } + + return icaAddresses +} + +func (s *WasmdTestSuite) getTrappedErrors(ctx context.Context, lpAddresses []string) []map[string]interface{} { + var trappedErrors []map[string]interface{} + for _, lpAddress := range lpAddresses { + var trappedErrors1 testsuite.ContractTrappedErrorsData + trappedErrors1Bytes := s.ExecuteContractQuery( + ctx, + s.Quasar(), + lpAddress, + map[string]any{ + "trapped_errors": map[string]any{}, + }, + ) + err := json.Unmarshal(trappedErrors1Bytes, &trappedErrors1) + s.Require().NoError(err) + trappedErrors = append(trappedErrors, trappedErrors1.Data.TrappedErrors) + } + + return trappedErrors +} diff --git a/tests/e2e/dockerfiles/osmosis.Dockerfile b/tests/e2e/dockerfiles/osmosis.Dockerfile index 15d574b5b..ac1b5b0cf 100644 --- a/tests/e2e/dockerfiles/osmosis.Dockerfile +++ b/tests/e2e/dockerfiles/osmosis.Dockerfile @@ -23,7 +23,7 @@ RUN git lfs install RUN git clone https://github.com/osmosis-labs/osmosis.git # Checkout specific version -RUN cd osmosis && git checkout v16.1.0 +RUN cd osmosis && git checkout v18.0.0 # Set Work Directory to osmosis WORKDIR osmosis diff --git a/tests/e2e/suite/types.go b/tests/e2e/suite/types.go index e6bc127b3..e579a81d1 100644 --- a/tests/e2e/suite/types.go +++ b/tests/e2e/suite/types.go @@ -9,14 +9,30 @@ type Accounts struct { Treasury ibc.Wallet } +type Address struct { + Address string `json:"address"` +} + type Balance struct { Balance string `json:"balance"` } +type TrappedErrors struct { + TrappedErrors map[string]interface{} `json:"errors"` +} + type ContractBalanceData struct { Data Balance `json:"data"` } +type ContractIcaAddressData struct { + Data Address `json:"data"` +} + +type ContractTrappedErrorsData struct { + Data TrappedErrors `json:"data"` +} + type PendingUnbondsData struct { Data Unbonds `json:"data"` } diff --git a/tests/e2e/suite/wasm.go b/tests/e2e/suite/wasm.go index aa0421877..25ad94dad 100644 --- a/tests/e2e/suite/wasm.go +++ b/tests/e2e/suite/wasm.go @@ -90,7 +90,8 @@ func (s *E2ETestSuite) ExecuteContract( keyName string, contractAddr string, funds sdk.Coins, - args any, result any, + args any, + result any, ) { tn := GetFullNode(chain) @@ -135,7 +136,7 @@ func (s *E2ETestSuite) ExecuteContractQuery(ctx context.Context, chain *cosmos.C return res } -func (s *E2ETestSuite) CreatePoolsOnOsmosis(ctx context.Context, chain *cosmos.CosmosChain, keyName string, poolBytes []byte) { +func (s *E2ETestSuite) CreatePoolOnOsmosis(ctx context.Context, chain *cosmos.CosmosChain, keyName string, poolBytes []byte, poolType string) { tn := GetFullNode(chain) logger := s.logger.With( @@ -154,12 +155,50 @@ func (s *E2ETestSuite) CreatePoolsOnOsmosis(ctx context.Context, chain *cosmos.C cmds = append(cmds, "--pool-file", filepath.Join(tn.HomeDir(), poolFile)) + if len(poolType) != 0 { + cmds = append(cmds, "--pool-type", poolType) + } + txhash, err := tn.ExecTx(ctx, keyName, cmds...) s.Require().NoError(err, "failed to create pool") s.AssertSuccessfulResultTx(ctx, chain, txhash, nil) } +func (s *E2ETestSuite) SwapTokenOnOsmosis(ctx context.Context, chain *cosmos.CosmosChain, keyName string, tokenIn string, tokenOutMinAmount string, flagSwapRouteDenoms string, flagSwapRoutePoolIds string) { + tn := GetFullNode(chain) + + cmds := []string{ + "gamm", "swap-exact-amount-in", + tokenIn, + tokenOutMinAmount, + "--swap-route-denoms", flagSwapRouteDenoms, + "--swap-route-pool-ids", flagSwapRoutePoolIds, + "--gas", "20000000", + } + + txhash, err := tn.ExecTx(ctx, keyName, cmds...) + s.Require().NoError(err, "failed to swap token") + + s.AssertSuccessfulResultTx(ctx, chain, txhash, nil) +} + +func (s *E2ETestSuite) JoinPoolOnOsmosis(ctx context.Context, chain *cosmos.CosmosChain, keyName string, poolId string, maxAmountsIn string, shareAmountOut string) { + tn := GetFullNode(chain) + + cmds := []string{ + "gamm", "join-pool", + "--gas", "20000000", "--pool-id", poolId, + "--max-amounts-in", maxAmountsIn, + "--share-amount-out", shareAmountOut, + } + + txhash, err := tn.ExecTx(ctx, keyName, cmds...) + s.Require().NoError(err, "failed to join pool") + + s.AssertSuccessfulResultTx(ctx, chain, txhash, nil) +} + func (s *E2ETestSuite) CreateStableswapPoolOnOsmosis(ctx context.Context, chain *cosmos.CosmosChain, keyName string, poolBytes []byte) { tn := GetFullNode(chain)