Skip to content

Commit

Permalink
Drop protocol support (#114)
Browse files Browse the repository at this point in the history
* Add drop protocol support

* add query tests

* cargo update

* fixes after merge

* add in smart swap queries

* get clippy to pass

* remove not required feature

* Rename state variables

---------

Co-authored-by: Jeremy Liu <[email protected]>
  • Loading branch information
albertandrejev and NotJeremyLiu authored Jul 29, 2024
1 parent cef851d commit 7fca8b8
Show file tree
Hide file tree
Showing 13 changed files with 1,224 additions and 140 deletions.
583 changes: 448 additions & 135 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ dexter-stable-pool = "1.1.1"
dexter-weighted-pool = "1.1.1"
dexter-lp-token = "1.0.0"
dexter-router = "1.1.0"
cosmwasm-schema = "1.5.0"
cosmwasm-std = { version = "1.5", features = ["stargate"] }
cosmwasm-schema = "1.5.4"
cosmwasm-std = { version = "1.5.4", features = ["stargate"] }
cosmos-sdk-proto = { version = "0.19", default-features = false }
cw2 = "1.1"
cw20 = "1.1"
Expand All @@ -37,12 +37,14 @@ cw-utils = "1.0.3"
cw-multi-test = "0.20.0"
ibc-proto = { version = "0.32.1", default-features = false }
lido-satellite = { git = "https://github.com/hadronlabs-org/lido-satellite", branch = "main", features = ["library"] }
drop-factory = { git = "https://github.com/hadronlabs-org/drop-contracts.git", branch = "main", features = ["library"] }
drop-staking-base = { git = "https://github.com/hadronlabs-org/drop-contracts.git", branch = "main", features = ["library"] }
neutron-proto = { version = "0.1.1", default-features = false, features = ["cosmwasm"] }
neutron-sdk = "0.8"
neutron-sdk = "0.10.0"
osmosis-std = "0.15.3"
prost = "0.11"
serde-cw-value = "0.7.0"
serde-json-wasm = "0.5.1"
serde-json-wasm = "1.0.1"
skip = { version = "0.3.0", path = "./packages/skip" }
test-case = "3.3.1"
thiserror = "1"
Expand Down
34 changes: 34 additions & 0 deletions contracts/adapters/swap/drop/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "skip-api-swap-adapter-drop"
version = { workspace = true }
rust-version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
documentation = { workspace = true }
keywords = { workspace = true }

[lib]
crate-type = ["cdylib", "rlib"]

[features]
# for more explicit tests, cargo test --features=backtraces
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all instantiate/execute/query exports
library = []

[dependencies]
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
cw2 = { workspace = true }
cw-storage-plus = { workspace = true }
cw-utils = { workspace = true }
skip = { workspace = true }
thiserror = { workspace = true }
drop-factory = { workspace = true }
drop-staking-base = { workspace = true }

[dev-dependencies]
test-case = { workspace = true }
3 changes: 3 additions & 0 deletions contracts/adapters/swap/drop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Drop Swap Adapter Contract

This contract is a simple swap adapter that treats the Drop core contract to be swapped through.
10 changes: 10 additions & 0 deletions contracts/adapters/swap/drop/src/bin/drop-schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use cosmwasm_schema::write_api;
use skip::swap::{DropBondInstantiateMsg as InstantiateMsg, ExecuteMsg, QueryMsg};

fn main() {
write_api! {
instantiate: InstantiateMsg,
execute: ExecuteMsg,
query: QueryMsg
}
}
318 changes: 318 additions & 0 deletions contracts/adapters/swap/drop/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
use crate::{
error::{ContractError, ContractResult},
state::{BONDED_DENOM, DROP_CORE_CONTRACT_ADDRESS, ENTRY_POINT_CONTRACT_ADDRESS, REMOTE_DENOM},
};
use cosmwasm_std::{
entry_point, to_json_binary, Binary, Coin, Decimal, Deps, DepsMut, Env, MessageInfo, Response,
WasmMsg,
};
use cw2::set_contract_version;
use cw_utils::one_coin;
use skip::{
asset::Asset,
swap::{
execute_transfer_funds_back, DropBondInstantiateMsg as InstantiateMsg, ExecuteMsg,
MigrateMsg, QueryMsg, SimulateSwapExactAssetInResponse, SimulateSwapExactAssetOutResponse,
SwapOperation,
},
};

///////////////
/// MIGRATE ///
///////////////

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> ContractResult<Response> {
unimplemented!()
}

///////////////////
/// INSTANTIATE ///
///////////////////

// Contract name and version used for migration.
const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> ContractResult<Response> {
// Set contract version
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

// Validate entry point contract address
let checked_entry_point_contract_address =
deps.api.addr_validate(&msg.entry_point_contract_address)?;

// Store the entry point contract address
ENTRY_POINT_CONTRACT_ADDRESS.save(deps.storage, &checked_entry_point_contract_address)?;

// Validate drop factory contract address
let checked_drop_factory_contract_address =
deps.api.addr_validate(&msg.drop_factory_contract_address)?;

let drop_factory_state: drop_factory::state::State = deps.querier.query_wasm_smart(
&checked_drop_factory_contract_address,
&drop_factory::msg::QueryMsg::State {},
)?;

// Store the drop core contract address
DROP_CORE_CONTRACT_ADDRESS.save(
deps.storage,
&deps.api.addr_validate(&drop_factory_state.core_contract)?,
)?;

let drop_core_config: drop_staking_base::state::core::Config = deps.querier.query_wasm_smart(
&checked_drop_factory_contract_address,
&drop_staking_base::msg::core::QueryMsg::Config {},
)?;

let bonded_denom = drop_core_config
.ld_denom
.ok_or(ContractError::BondedDenomNotSet {})?;

BONDED_DENOM.save(deps.storage, &bonded_denom)?;
REMOTE_DENOM.save(deps.storage, &drop_core_config.remote_denom)?;

Ok(Response::new()
.add_attribute("action", "instantiate")
.add_attribute(
"entry_point_contract_address",
checked_entry_point_contract_address.to_string(),
)
.add_attribute(
"drop_factory_contract_address",
checked_drop_factory_contract_address.to_string(),
)
.add_attribute(
"drop_core_contract_address",
drop_factory_state.core_contract,
)
.add_attribute("bonded_denom", bonded_denom)
.add_attribute("remote_denom", drop_core_config.remote_denom))
}

///////////////
/// EXECUTE ///
///////////////

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> ContractResult<Response> {
match msg {
ExecuteMsg::Swap { operations } => execute_swap(deps, env, info, operations),
ExecuteMsg::TransferFundsBack {
swapper,
return_denom,
} => Ok(execute_transfer_funds_back(
deps,
env,
info,
swapper,
return_denom,
)?),
_ => {
unimplemented!()
}
}
}

fn execute_swap(
deps: DepsMut,
env: Env,
info: MessageInfo,
_operations: Vec<SwapOperation>,
) -> ContractResult<Response> {
// Get entry point contract address from storage
let entry_point_contract_address = ENTRY_POINT_CONTRACT_ADDRESS.load(deps.storage)?;

// Enforce the caller is the entry point contract
if info.sender != entry_point_contract_address {
return Err(ContractError::Unauthorized);
}

// Get coin in from the message info, error if there is not exactly one coin sent
let coin_in = one_coin(&info)?;

let remote_denom = REMOTE_DENOM.load(deps.storage)?;
let bonded_denom = BONDED_DENOM.load(deps.storage)?;

// Decide which message to Core contract should be emitted
let (drop_core_msg, return_denom) = if coin_in.denom == remote_denom {
(
drop_staking_base::msg::core::ExecuteMsg::Bond { receiver: None },
bonded_denom,
)
} else {
return Err(ContractError::UnsupportedDenom);
};

let drop_core_contract_address = DROP_CORE_CONTRACT_ADDRESS.load(deps.storage)?;

let swap_msg = WasmMsg::Execute {
contract_addr: drop_core_contract_address.to_string(),
msg: to_json_binary(&drop_core_msg)?,
funds: vec![coin_in],
};

// Create the transfer funds back message
let transfer_funds_back_msg = WasmMsg::Execute {
contract_addr: env.contract.address.to_string(),
msg: to_json_binary(&ExecuteMsg::TransferFundsBack {
swapper: info.sender,
return_denom,
})?,
funds: vec![],
};

Ok(Response::new()
.add_message(swap_msg)
.add_message(transfer_funds_back_msg)
.add_attribute("action", "dispatch_swap_and_transfer_back"))
}

/////////////
/// QUERY ///
/////////////

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult<Binary> {
let remote_denom = REMOTE_DENOM.load(deps.storage)?;
let bonded_denom = BONDED_DENOM.load(deps.storage)?;

match msg {
QueryMsg::SimulateSwapExactAssetIn { asset_in, .. } => {
let asset_out_denom =
get_opposite_denom(asset_in.denom(), &remote_denom, &bonded_denom);

let exchange_rate = get_exchange_rate(deps)?;

to_json_binary(&Asset::Native(Coin::new(
(exchange_rate * asset_in.amount()).into(),
asset_out_denom,
)))
}
QueryMsg::SimulateSwapExactAssetOut { asset_out, .. } => {
let asset_in_denom =
get_opposite_denom(asset_out.denom(), &remote_denom, &bonded_denom);

let exchange_rate = get_exchange_rate(deps)?;

to_json_binary(&Asset::Native(Coin::new(
(asset_out.amount().div_floor(exchange_rate)).into(),
asset_in_denom,
)))
}
QueryMsg::SimulateSwapExactAssetInWithMetadata {
asset_in,
include_spot_price,
..
} => {
let asset_out_denom =
get_opposite_denom(asset_in.denom(), &remote_denom, &bonded_denom);

let exchange_rate = get_exchange_rate(deps)?;

let spot_price = if include_spot_price {
Some(exchange_rate)
} else {
None
};

to_json_binary(&SimulateSwapExactAssetInResponse {
asset_out: Asset::Native(Coin::new(
(exchange_rate * asset_in.amount()).into(),
asset_out_denom,
)),
spot_price,
})
}
QueryMsg::SimulateSwapExactAssetOutWithMetadata {
asset_out,
include_spot_price,
..
} => {
let asset_in_denom =
get_opposite_denom(asset_out.denom(), &remote_denom, &bonded_denom);

let exchange_rate = get_exchange_rate(deps)?;

let spot_price = if include_spot_price {
Some(exchange_rate)
} else {
None
};

to_json_binary(&SimulateSwapExactAssetOutResponse {
asset_in: Asset::Native(Coin::new(
(asset_out.amount().div_floor(exchange_rate)).into(),
asset_in_denom,
)),
spot_price,
})
}
QueryMsg::SimulateSmartSwapExactAssetIn { asset_in, .. } => {
let asset_out_denom =
get_opposite_denom(asset_in.denom(), &remote_denom, &bonded_denom);

let exchange_rate = get_exchange_rate(deps)?;

to_json_binary(&Asset::Native(Coin::new(
(exchange_rate * asset_in.amount()).into(),
asset_out_denom,
)))
}
QueryMsg::SimulateSmartSwapExactAssetInWithMetadata {
asset_in,
include_spot_price,
..
} => {
let asset_out_denom =
get_opposite_denom(asset_in.denom(), &remote_denom, &bonded_denom);

let exchange_rate = get_exchange_rate(deps)?;

let spot_price = if include_spot_price {
Some(exchange_rate)
} else {
None
};

to_json_binary(&SimulateSwapExactAssetInResponse {
asset_out: Asset::Native(Coin::new(
(exchange_rate * asset_in.amount()).into(),
asset_out_denom,
)),
spot_price,
})
}
}
.map_err(From::from)
}

fn get_opposite_denom(denom: &str, remote_denom: &str, bonded_denom: &str) -> String {
match denom {
denom if denom == remote_denom => bonded_denom.to_string(),
denom if denom == bonded_denom => remote_denom.to_string(),
_ => unimplemented!(),
}
}

fn get_exchange_rate(deps: Deps) -> ContractResult<Decimal> {
let drop_core_contract_address = DROP_CORE_CONTRACT_ADDRESS.load(deps.storage)?;

let exchange_rate: Decimal = deps.querier.query_wasm_smart(
drop_core_contract_address,
&drop_staking_base::msg::core::QueryMsg::ExchangeRate {},
)?;

Ok(exchange_rate)
}
Loading

0 comments on commit 7fca8b8

Please sign in to comment.