Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop protocol support #114

Merged
merged 8 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading