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

fix/feat: add fix for multi-hop buys and contract migration system #15

Merged
merged 7 commits into from
Feb 25, 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
2 changes: 1 addition & 1 deletion .github/workflows/rust.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
toolchain: 1.74.0
args: --locked --tests
env:
LLVM_PROFILE_FILE: "swap-converter-%p-%m.profraw"
LLVM_PROFILE_FILE: "swap-contract-%p-%m.profraw"
RUSTFLAGS: "-Cinstrument-coverage"
RUST_BACKTRACE: 1

Expand Down
51 changes: 26 additions & 25 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Changelog

All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

-

### Changed

-

### Fixed

-

## [Version v1.1.0] - 2024-02-24

### Added

- This Changelog file
- `rust-toolchain` file
- Contract migration templates and migrate function

### Changed

-

### Fixed

- Correctly round to `min_quantity_tick_size` in intermediate steps for multi-hop buys

## [Version v1.0.0] - 2024-02-16

### Added

- Versioning into contract

### Changed

- Updated to latest CosmWasm version

### Fixed

-
5 changes: 3 additions & 2 deletions contracts/swap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
authors = [ "Markus Waas <[email protected]>" ]
edition = "2021"
name = "injective-converter"
version = "1.0.0"
gorgos marked this conversation as resolved.
Show resolved Hide resolved
name = "swap-contract"
version = "1.0.1"

exclude = [
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
Expand Down Expand Up @@ -45,6 +45,7 @@ serde-json-wasm = "0.5.1"
thiserror = { version = "1.0.31" }

[dev-dependencies]
cosmos-sdk-proto = { version = "0.19.0", default-features = false }
cosmwasm-schema = "1.5.0"
cw-multi-test = "0.16.2"
injective-std = { version = "0.1.5" }
Expand Down
2 changes: 2 additions & 0 deletions contracts/swap/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub fn save_config(
fee_recipient,
admin,
};
config.to_owned().validate()?;

CONFIG.save(deps.storage, &config)
}

Expand Down
54 changes: 51 additions & 3 deletions contracts/swap/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ use cosmwasm_std::{
use cw2::{get_contract_version, set_contract_version};

use crate::admin::{delete_route, save_config, set_route, update_config, withdraw_support_funds};
use crate::helpers::handle_config_migration;
use crate::types::{ConfigResponse, SwapQuantityMode};
use injective_cosmwasm::{InjectiveMsgWrapper, InjectiveQueryWrapper};

use crate::error::ContractError;

use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use crate::queries::{estimate_swap_result, SwapQuantity};
use crate::state::{get_all_swap_routes, get_config, read_swap_route};
use crate::swap::{handle_atomic_order_reply, start_swap_flow};

// version info for migration info
pub const CONTRACT_NAME: &str = "crates.io:atomic-order-example";
pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

pub const ATOMIC_ORDER_REPLY_ID: u64 = 1u64;
Expand All @@ -32,6 +32,7 @@ pub fn instantiate(
) -> Result<Response<InjectiveMsgWrapper>, ContractError> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
save_config(deps, env, msg.admin, msg.fee_recipient)?;

Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("owner", info.sender))
Expand Down Expand Up @@ -98,6 +99,53 @@ pub fn reply(
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(
deps: DepsMut<InjectiveQueryWrapper>,
_env: Env,
_msg: MigrateMsg,
) -> Result<Response, ContractError> {
let contract_version = get_contract_version(deps.storage)?;
gorgos marked this conversation as resolved.
Show resolved Hide resolved

match contract_version.contract.as_ref() {
// old contract name
"crates.io:atomic-order-example" => match contract_version.version.as_ref() {
"0.1.0" => {
unimplemented!(
"Migration from version {} is no longer supported",
contract_version.version
);
}
"1.0.0" => {
set_contract_version(
deps.storage,
format!("crates.io:{CONTRACT_NAME}"),
CONTRACT_VERSION,
)?;

handle_config_migration(deps)?;
}
_ => return Err(ContractError::MigrationError {}),
},
"crates.io:swap-contract" => match contract_version.version.as_ref() {
"1.0.1" => {
gorgos marked this conversation as resolved.
Show resolved Hide resolved
unimplemented!(
"Migration from version {} is no yet supported",
contract_version.version
);
}
_ => return Err(ContractError::MigrationError {}),
},
_ => return Err(ContractError::MigrationError {}),
}

Ok(Response::new()
.add_attribute("previous_contract_name", &contract_version.contract)
.add_attribute("previous_contract_version", &contract_version.version)
.add_attribute("new_contract_name", format!("crates.io:{CONTRACT_NAME}"))
.add_attribute("new_contract_version", CONTRACT_VERSION))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps<InjectiveQueryWrapper>, env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
Expand Down
5 changes: 4 additions & 1 deletion contracts/swap/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub enum ContractError {
#[error("Failure response from submsg: {0}")]
SubMsgFailure(String),

#[error("Unrecognised reply id: {0}")]
#[error("Unrecognized reply id: {0}")]
UnrecognizedReply(u64),

#[error("Invalid reply from sub-message {id}, {err}")]
Expand All @@ -27,4 +27,7 @@ pub enum ContractError {

#[error("Provided amount of {0} is below required amount of {1}")]
InsufficientFundsProvided(FPDecimal, FPDecimal),

#[error("Contract can't be migrated")]
MigrationError {},
}
27 changes: 25 additions & 2 deletions contracts/swap/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use cosmwasm_std::{CosmosMsg, SubMsg};
use cosmwasm_std::{CosmosMsg, DepsMut, Response, SubMsg};

use injective_cosmwasm::InjectiveMsgWrapper;
use cw_storage_plus::Item;
use injective_cosmwasm::{InjectiveMsgWrapper, InjectiveQueryWrapper};
use injective_math::FPDecimal;

use crate::{state::CONFIG, types::Config, ContractError};

pub fn i32_to_dec(source: i32) -> FPDecimal {
FPDecimal::from(i128::from(source))
}
Expand Down Expand Up @@ -49,6 +52,26 @@ pub fn dec_scale_factor() -> FPDecimal {
FPDecimal::ONE.scaled(18)
}

type V100Config = Config;
const V100CONFIG: Item<V100Config> = Item::new("config");

pub fn handle_config_migration(
deps: DepsMut<InjectiveQueryWrapper>,
) -> Result<Response, ContractError> {
let v100_config = V100CONFIG.load(deps.storage)?;

let config = Config {
fee_recipient: v100_config.fee_recipient,
admin: v100_config.admin,
};

CONFIG.save(deps.storage, &config)?;

config.validate()?;

Ok(Response::default())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
3 changes: 3 additions & 0 deletions contracts/swap/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub struct InstantiateMsg {
pub admin: Addr,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct MigrateMsg {}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Expand Down
16 changes: 7 additions & 9 deletions contracts/swap/src/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,22 +230,19 @@ fn estimate_execution_buy_from_target(
fee_percent: FPDecimal,
is_simulation: bool,
) -> StdResult<StepExecutionEstimate> {
if !(target_base_output_quantity.num % market.min_quantity_tick_size.num).is_zero() {
return Err(StdError::generic_err(
"Target quantity must be a multiple of min_quantity_tick_size",
));
}
let rounded_target_base_output_quantity =
round_up_to_min_tick(target_base_output_quantity, market.min_quantity_tick_size);

let orders = querier.query_spot_market_orderbook(
&market.market_id,
OrderSide::Sell,
Some(target_base_output_quantity),
Some(rounded_target_base_output_quantity),
None,
)?;
let top_orders = get_minimum_liquidity_levels(
deps,
&orders.sells_price_level,
target_base_output_quantity,
rounded_target_base_output_quantity,
|l| l.q,
market.min_quantity_tick_size,
)?;
Expand All @@ -255,12 +252,13 @@ fn estimate_execution_buy_from_target(
get_average_price_from_orders(&top_orders, market.min_price_tick_size, true);
let worst_price = get_worst_price_from_orders(&top_orders);

let expected_exchange_quote_quantity = target_base_output_quantity * average_price;
let expected_exchange_quote_quantity = rounded_target_base_output_quantity * average_price;
let fee_estimate = expected_exchange_quote_quantity * fee_percent;
let required_input_quote_quantity = expected_exchange_quote_quantity + fee_estimate;

// check if user funds + contract funds are enough to create order
let required_funds = worst_price * target_base_output_quantity * (FPDecimal::ONE + fee_percent);
let required_funds =
worst_price * rounded_target_base_output_quantity * (FPDecimal::ONE + fee_percent);

let funds_in_contract = deps
.querier
Expand Down
6 changes: 6 additions & 0 deletions contracts/swap/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ pub const STEP_STATE: Item<CurrentSwapStep> = Item::new("current_step_cache");
pub const SWAP_RESULTS: Item<Vec<SwapResults>> = Item::new("swap_results");
pub const CONFIG: Item<Config> = Item::new("config");

impl Config {
pub fn validate(self) -> StdResult<()> {
Ok(())
}
}

pub fn store_swap_route(storage: &mut dyn Storage, route: &SwapRoute) -> StdResult<()> {
let key = route_key(&route.source_denom, &route.target_denom);
SWAP_ROUTES.save(storage, key, route)
Expand Down
Loading
Loading