Skip to content

Commit

Permalink
Merge pull request #3 from 0xsquid/feat/osmosis-swap-support
Browse files Browse the repository at this point in the history
Feat/osmosis swap support
  • Loading branch information
nnoln authored Dec 1, 2023
2 parents e81db65 + ccaf002 commit 01b7c0b
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 131 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

3 changes: 2 additions & 1 deletion contracts/multicall/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "multicall"
version = "1.2.1"
version = "1.2.2"
authors = ["0xsquid"]
edition = "2021"

Expand Down Expand Up @@ -31,5 +31,6 @@ enum-repr = { workspace = true }
prost = { workspace = true }

cw20 = "1.1.0"

ibc-tracking = { version = "1.2.0", path = "../../packages/ibc-tracking" }
shared = { version = "1.2.0", path = "../../packages/shared" }
13 changes: 7 additions & 6 deletions contracts/multicall/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Executes a set of cosmos messages specified in the calls array
"msg": {},
"actions": []
}
]
],
"fallback_address": "<local_fallback_address>",
}
}
```
Expand Down Expand Up @@ -106,13 +107,13 @@ Converts specified field into [`Binary`] type

### `field_to_proto_binary`
Converts specified field into [`Binary`] type encoded using [`prost::Message::encode`] method.
Note: since the type of the message should be known to the contract at the moment only ibc transfer is supported.
Note: since the type of the message should be known to the contract at the moment only ibc transfer and osmosis swap exact amount in is supported.

```json
{
"field_to_proto_binary": {
"replacer": "/path/to/field/for/replacement",
"proto_msg_type": "ibc_transfer"
"proto_msg_type": "ibc_transfer" | "osmosis_swap_exact_amt_in"
}
}
```
Expand Down Expand Up @@ -155,9 +156,9 @@ Replacer is basically a path from the root of the `msg` object to a field. For e
`replacer` value is equals to a path to `amount` field inside bank message. This field will be replaced with fetched contract balance of `uosmo` coin. Numbers in the path are equal to an index in the array.

## Fallback address
Fallback address is an optional field that could be set for:
Fallback address is a field that must be set for:
- ibc error recovery when `ibc_tracking` action is enabled
- local funds recovery in case of contract execution failure, e.g. multicall contract was trying to perform a swap and failed because of price change - so instead of forwarding dex error multicall contract will recover all owned funds to the specified fallback address. Note: if fallback address is not set or there is no funds to recover - the contract will forward an error.
- local funds recovery in case of contract execution failure or any funds left on the contract balance after successful execution, e.g. multicall contract was trying to perform a swap and failed because of price change - so instead of forwarding dex error multicall contract will recover all owned funds to the specified fallback address. Note: if fallback address is not set or there is no funds to recover - the contract will forward an error.


## Example calls
Expand Down Expand Up @@ -311,4 +312,4 @@ Second - since `msg` field in a wasm call must be a base64 encoded Binary object
}
```

Note: since there is no updates for wasm message field it can be alredy specified as a binary value. Here it is shown for explanaition purposes.
Note: since there is no updates for wasm message field it can be alredy specified as a binary value. Here it is shown for explanaition purposes.
25 changes: 18 additions & 7 deletions contracts/multicall/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ibc_tracking::{
msg::{CwIbcMessage, MsgTransfer},
state::{store_ibc_transfer_reply_state, IbcTransferReplyState},
};
use osmosis_std::types::osmosis::gamm::v1beta1::MsgSwapExactAmountIn;
use shared::{util::json_pointer, SerializableJson};
use std::str::FromStr;

Expand All @@ -23,7 +24,7 @@ impl Call {
storage: &mut dyn Storage,
querier: &QuerierWrapper<SerializableJson>,
env: &Env,
fallback_address: &Option<String>,
fallback_address: &str,
) -> Result<SubMsg<SerializableJson>, ContractError> {
let mut cosmos_msg = self.msg.0.clone();
let mut reply_id = MsgReplyId::ProcessCall.repr();
Expand Down Expand Up @@ -95,9 +96,7 @@ impl Call {
} => {
reply_id = MsgReplyId::IbcTransferTracking.repr();

let Some(local_fallback_address) = fallback_address.clone() else {
return Err(ContractError::FallbackAddressMustBeSetForIbcTracking {});
};
let local_fallback_address = fallback_address.to_owned();

let amount = if let Some(pointer) = amount_pointer {
let amount_field = json_pointer(&mut cosmos_msg, pointer).ok_or(
Expand Down Expand Up @@ -153,11 +152,15 @@ impl Call {
.map_err(|_| ContractError::ProtoSerializationError {})?
.into();

let mut bytes = Vec::new();
prost::Message::encode(&ibc, &mut bytes)
self.encode_proto_msg(&ibc)?
}
ProtoMessageType::OsmosisSwapExactAmtIn => {
let swap: MsgSwapExactAmountIn = binary_field
.clone()
.deserialize_into::<MsgSwapExactAmountIn>()
.map_err(|_| ContractError::ProtoSerializationError {})?;

Binary(bytes)
self.encode_proto_msg(&swap)?
}
};

Expand Down Expand Up @@ -188,4 +191,12 @@ impl Call {
*field = serde_cw_value::Value::String(value.to_owned());
Ok(())
}

fn encode_proto_msg<T: prost::Message>(&self, msg: &T) -> Result<Binary, ContractError> {
let mut bytes = Vec::new();
prost::Message::encode(msg, &mut bytes)
.map_err(|_| ContractError::ProtoSerializationError {})?;

Ok(Binary(bytes))
}
}
22 changes: 16 additions & 6 deletions contracts/multicall/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ pub fn handle_multicall(
deps: DepsMut<SerializableJson>,
env: &Env,
calls: &[Call],
fallback_address: &Option<String>,
fallback_address: &str,
) -> Result<Response<SerializableJson>, ContractError> {
if multicall_state_exists(deps.storage)? {
return Err(ContractError::ContractLocked {
msg: "Another multicall action is in progress".to_owned(),
});
}

let state = MulticallState::new(calls.to_owned().as_mut(), fallback_address.clone())?;
let state = MulticallState::new(calls.to_owned().as_mut(), fallback_address.to_owned())?;
store_multicall_state(deps.storage, &state)?;

Ok(Response::new().add_submessage(SubMsg::reply_on_error(
Expand Down Expand Up @@ -76,7 +76,19 @@ pub fn handle_call(
let Some(call) = state.next_call() else {
// if there is no calls left then finish the execution here
remove_multicall_state(deps.storage)?;
return Ok(Response::new().add_attribute("multicall_execution", "success"));

let mut response: Response<SerializableJson> = Response::new().add_attribute("multicall_execution", "success");

// query contracts balance for any leftover funds after calls execution and if anything left then transfer it to the fallback address
let leftover_funds = deps.querier.query_all_balances(env.contract.address.as_str())?;
if !leftover_funds.is_empty() {
response = response.add_message(BankMsg::Send {
to_address: fallback_address,
amount: leftover_funds,
}).add_attribute("leftover_funds", "recovered");
}

return Ok(response);
};

let submsg = call.try_into_msg(deps.storage, &deps.querier, env, &fallback_address)?;
Expand Down Expand Up @@ -145,9 +157,7 @@ pub fn handle_execution_fallback_reply(
let state = load_multicall_state(deps.storage)?;
remove_multicall_state(deps.storage)?;

let Some(fallback_address) = state.fallback_address else {
return Err(ContractError::RecoveryError { msg: "Fallback address is not set for local funds recovery".to_owned(), origin_err });
};
let fallback_address = state.fallback_address;

let recover_funds = deps
.querier
Expand Down
3 changes: 0 additions & 3 deletions contracts/multicall/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ pub enum ContractError {
#[error("Calls list is empty")]
EmptyCallsList {},

#[error("Fallback address must be set for Ibc tracking")]
FallbackAddressMustBeSetForIbcTracking {},

#[error("Invalid call action argument: {msg}")]
InvalidCallActionArgument { msg: String },

Expand Down
4 changes: 3 additions & 1 deletion contracts/multicall/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub enum ExecuteMsg {
/// onchain calls to perform
calls: Vec<Call>,
/// fallback address for failed/timeout rejected ibc transfers
fallback_address: Option<String>,
fallback_address: String,
},
/// ## Description
/// Internal action, can be called only by the contract itself
Expand Down Expand Up @@ -145,4 +145,6 @@ pub struct ReplaceInfo {
pub enum ProtoMessageType {
/// ibc message type
IbcTransfer,
/// osmosis gamm swap exact amount in type
OsmosisSwapExactAmtIn,
}
18 changes: 2 additions & 16 deletions contracts/multicall/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,13 @@ pub struct MulticallState {
/// onchain calls to perform
pub calls: Vec<Call>,
/// fallback address for failed/timeout rejected ibc transfers
pub fallback_address: Option<String>,
pub fallback_address: String,
}

impl MulticallState {
/// ## Description
/// Creates new instance of [`MulticallState`] struct
pub fn new(
calls: &mut [Call],
fallback_address: Option<String>,
) -> Result<Self, ContractError> {
pub fn new(calls: &mut [Call], fallback_address: String) -> Result<Self, ContractError> {
calls.iter_mut().for_each(|call| call.actions.sort());

let state = Self {
Expand Down Expand Up @@ -60,17 +57,6 @@ impl MulticallState {
return Err(ContractError::EmptyCallsList {});
}

// if ibc tracking is required then fallback address must be set
let has_ibc_tracking = self
.calls
.iter()
.flat_map(|call| call.actions.clone())
.any(|action| matches!(action, CallAction::IbcTracking { .. }));

if has_ibc_tracking && self.fallback_address.is_none() {
return Err(ContractError::FallbackAddressMustBeSetForIbcTracking {});
}

for call in self.calls.iter() {
let ibc_tracking_count = call
.actions
Expand Down
Loading

0 comments on commit 01b7c0b

Please sign in to comment.