Skip to content

Commit 1e3e2d2

Browse files
committed
Merge branch 'tiago/min-output-amount-from-slippage' (#4723)
* origin/tiago/min-output-amount-from-slippage: Changelog for #4723 Encode Osmosis swap receivers in compat mode Test compat taddr serialization Encode taddrs in compat mode with bech32 Improve thread join err msg Compute min output amount from TWAP
2 parents 6fa0789 + 7375624 commit 1e3e2d2

File tree

7 files changed

+311
-139
lines changed

7 files changed

+311
-139
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- Improve the UX of Osmosis swaps in various ways. Moreover, start encoding the
2+
MASP address with `bech32`, rather than `bech32m`, when shielding the output
3+
of the trade. ([\#4723](https://github.com/anoma/namada/pull/4723))

crates/apps_lib/src/cli.rs

Lines changed: 39 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3611,7 +3611,8 @@ pub mod args {
36113611
pub const NO_CONVERSIONS: ArgFlag = flag("no-conversions");
36123612
pub const NO_EXPIRATION: ArgFlag = flag("no-expiration");
36133613
pub const NUT: ArgFlag = flag("nut");
3614-
pub const OSMOSIS_REST_RPC: Arg<String> = arg("osmosis-rest-rpc");
3614+
pub const OSMOSIS_LCD_RPC: ArgOpt<String> = arg_opt("osmosis-lcd");
3615+
pub const OSMOSIS_SQS_RPC: ArgOpt<String> = arg_opt("osmosis-sqs");
36153616
pub const OUT_FILE_PATH_OPT: ArgOpt<PathBuf> = arg_opt("out-file-path");
36163617
pub const OUTPUT: ArgOpt<PathBuf> = arg_opt("output");
36173618
pub const OUTPUT_DENOM: Arg<String> = arg("output-denom");
@@ -3673,7 +3674,7 @@ pub mod args {
36733674
pub const SENDER: Arg<String> = arg("sender");
36743675
pub const SHIELDED: ArgFlag = flag("shielded");
36753676
pub const SHOW_IBC_TOKENS: ArgFlag = flag("show-ibc-tokens");
3676-
pub const SLIPPAGE: ArgOpt<f64> = arg_opt("slippage-percentage");
3677+
pub const SLIPPAGE: ArgOpt<Dec> = arg_opt("slippage-percentage");
36773678
pub const SIGNING_KEYS: ArgMulti<WalletPublicKey, GlobStar> =
36783679
arg_multi("signing-keys");
36793680
pub const SIGNATURES: ArgMulti<PathBuf, GlobStar> = arg_multi("signatures");
@@ -3734,7 +3735,6 @@ pub mod args {
37343735
pub const WASM_DIR: ArgOpt<PathBuf> = arg_opt("wasm-dir");
37353736
pub const WASM_PATH: ArgOpt<PathBuf> = arg_opt("wasm-path");
37363737
pub const WEBSITE_OPT: ArgOpt<String> = arg_opt("website");
3737-
pub const WINDOW_SECONDS: ArgOpt<u64> = arg_opt("window-seconds");
37383738
pub const WITH_INDEXER: ArgOpt<String> = arg_opt("with-indexer");
37393739
pub const WRAPPER_SIGNATURE_OPT: ArgOpt<PathBuf> = arg_opt("gas-signature");
37403740
pub const TX_PATH: Arg<PathBuf> = arg("tx-path");
@@ -5241,49 +5241,41 @@ pub mod args {
52415241
slippage: self.slippage,
52425242
local_recovery_addr: self.local_recovery_addr,
52435243
route: self.route,
5244-
osmosis_rest_rpc: self.osmosis_rest_rpc,
5244+
osmosis_lcd_rpc: self.osmosis_lcd_rpc,
5245+
osmosis_sqs_rpc: self.osmosis_sqs_rpc,
52455246
})
52465247
}
52475248
}
52485249

52495250
impl Args for TxOsmosisSwap<CliTypes> {
52505251
fn parse(matches: &ArgMatches) -> Self {
52515252
let transfer = TxIbcTransfer::parse(matches);
5252-
let osmosis_rest_rpc = OSMOSIS_REST_RPC.parse(matches);
5253+
let osmosis_lcd_rpc = OSMOSIS_LCD_RPC.parse(matches);
5254+
let osmosis_sqs_rpc = OSMOSIS_SQS_RPC.parse(matches);
52535255
let output_denom = OUTPUT_DENOM.parse(matches);
52545256
let maybe_trans_recipient = TARGET_OPT.parse(matches);
52555257
let maybe_shielded_recipient =
52565258
PAYMENT_ADDRESS_TARGET_OPT.parse(matches);
52575259
let maybe_overflow = OVERFLOW_OPT.parse(matches);
52585260
let slippage_percent = SLIPPAGE.parse(matches);
5259-
if slippage_percent
5260-
.is_some_and(|percent| !(0.0..=100.0).contains(&percent))
5261-
{
5261+
if slippage_percent.is_some_and(|percent| {
5262+
let zero = Dec::zero();
5263+
let hundred = Dec::new(100, 0).unwrap();
5264+
5265+
percent < zero || percent > hundred
5266+
}) {
52625267
panic!(
52635268
"The slippage percent must be a number between 0 and 100."
52645269
)
52655270
}
5266-
let window_seconds = WINDOW_SECONDS.parse(matches);
52675271
let minimum_amount = MINIMUM_AMOUNT.parse(matches);
52685272
let slippage = minimum_amount
52695273
.map(|d| Slippage::MinOutputAmount(d.redenominate(0).amount()))
52705274
.or_else(|| {
52715275
Some(Slippage::Twap {
5272-
slippage_percentage: slippage_percent
5273-
.expect(
5274-
"If a minimum amount was not provided, \
5275-
slippage-percentage and window-seconds must \
5276-
be specified.",
5277-
)
5278-
.to_string(),
5279-
window_seconds: window_seconds.expect(
5280-
"If a minimum amount was not provided, \
5281-
slippage-percentage and window-seconds must be \
5282-
specified.",
5283-
),
5276+
slippage_percentage: slippage_percent?,
52845277
})
5285-
})
5286-
.unwrap();
5278+
});
52875279
let local_recovery_addr = LOCAL_RECOVERY_ADDR.parse(matches);
52885280
let route = match OSMOSIS_POOL_HOP.parse(matches) {
52895281
r if r.is_empty() => None,
@@ -5301,23 +5293,31 @@ pub mod args {
53015293
slippage,
53025294
local_recovery_addr,
53035295
route,
5304-
osmosis_rest_rpc,
5296+
osmosis_lcd_rpc,
5297+
osmosis_sqs_rpc,
53055298
}
53065299
}
53075300

53085301
fn def(app: App) -> App {
53095302
app.add_args::<TxIbcTransfer<CliTypes>>()
53105303
.arg(
5311-
OSMOSIS_REST_RPC
5304+
OSMOSIS_LCD_RPC
53125305
.def()
5313-
.help(wrap!("A url pointing to an Osmosis REST rpc.")),
5306+
.help(wrap!("URL pointing to an Osmosis lcd rpc.")),
53145307
)
5315-
.arg(OSMOSIS_POOL_HOP.def().help(wrap!(
5316-
"Individual hop of the route to take through Osmosis \
5317-
pools. This value takes the form \
5318-
<osmosis-pool-id>:<pool-output-denom>. When unspecified, \
5319-
the optimal route is queried on the fly."
5320-
)))
5308+
.arg(
5309+
OSMOSIS_SQS_RPC
5310+
.def()
5311+
.help(wrap!("URL pointing to an Osmosis sqs rpc.")),
5312+
)
5313+
.arg(OSMOSIS_POOL_HOP.def().conflicts_with(SLIPPAGE.name).help(
5314+
wrap!(
5315+
"Individual hop of the route to take through Osmosis \
5316+
pools. This value takes the form \
5317+
<osmosis-pool-id>:<pool-output-denom>. When \
5318+
unspecified, the optimal route is queried on the fly."
5319+
),
5320+
))
53215321
.arg(OUTPUT_DENOM.def().help(wrap!(
53225322
"IBC trace path (on Namada) of the desired asset. This is \
53235323
a string of the form \
@@ -5353,34 +5353,21 @@ pub mod args {
53535353
of the trade. If unspecified, a disposable address is \
53545354
generated."
53555355
)))
5356-
.arg(SLIPPAGE.def().requires(WINDOW_SECONDS.name).help(wrap!(
5356+
.arg(SLIPPAGE.def().help(wrap!(
53575357
"Slippage percentage, as a number between 0 and 100. \
53585358
Represents the maximum acceptable deviation from the \
53595359
expected price during a trade."
53605360
)))
5361-
.arg(WINDOW_SECONDS.def().requires(SLIPPAGE.name).help(wrap!(
5362-
"Time period (in seconds) over which the average price is \
5363-
calculated."
5364-
)))
5365-
.arg(
5366-
MINIMUM_AMOUNT
5367-
.def()
5368-
.conflicts_with(SLIPPAGE.name)
5369-
.conflicts_with(WINDOW_SECONDS.name)
5370-
.help(wrap!(
5371-
"Minimum amount of target asset that the trade \
5372-
should produce."
5373-
)),
5374-
)
5361+
.arg(MINIMUM_AMOUNT.def().conflicts_with(SLIPPAGE.name).help(
5362+
wrap!(
5363+
"Minimum amount of target asset that the trade should \
5364+
produce."
5365+
),
5366+
))
53755367
.arg(LOCAL_RECOVERY_ADDR.def().help(wrap!(
53765368
"Address on Osmosis from which to recover funds in case \
53775369
of failure."
53785370
)))
5379-
.group(
5380-
ArgGroup::new("slippage")
5381-
.args([SLIPPAGE.name, MINIMUM_AMOUNT.name])
5382-
.required(true),
5383-
)
53845371
.group(
53855372
ArgGroup::new("transfer-target")
53865373
.args([

crates/apps_lib/src/cli/client.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,48 @@ impl CliApi {
125125

126126
let args = args.to_sdk(&mut ctx)?;
127127
let namada = ctx.to_sdk(client, io);
128-
let args = args.into_ibc_transfer(&namada).await?;
128+
let args = args
129+
.into_ibc_transfer(
130+
&namada,
131+
|_route, min_amount, quote_amount| {
132+
use std::io::Write;
133+
134+
if let Some(quote_amount) = quote_amount {
135+
print!(
136+
"Minimum output is {min_amount}, \
137+
while the quote price is \
138+
{quote_amount}, proceed with \
139+
trade (y/n)? "
140+
);
141+
} else {
142+
print!(
143+
"Minimum output is {min_amount}, \
144+
proceed with trade (y/n)? "
145+
);
146+
}
147+
148+
std::io::stdout()
149+
.flush()
150+
.expect("Failed to flush stdout");
151+
152+
let line = {
153+
let mut line = String::new();
154+
std::io::stdin()
155+
.read_line(&mut line)
156+
.expect(
157+
"Failed to read line from \
158+
stdin",
159+
);
160+
line
161+
};
162+
163+
matches!(
164+
line.trim(),
165+
"y" | "Y" | "yes" | "YES" | "Yes"
166+
)
167+
},
168+
)
169+
.await?;
129170

130171
tx::submit_ibc_transfer(&namada, args).await?;
131172
}

crates/core/src/address.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,20 @@ impl Address {
278278
string_encoding::Format::encode(self)
279279
}
280280

281+
/// Encode an address in compatibility mode (i.e. with the legacy Bech32
282+
/// encoding)
283+
pub fn encode_compat(&self) -> String {
284+
use crate::string_encoding::Format;
285+
286+
bech32::encode::<bech32::Bech32>(Self::HRP, self.to_bytes().as_ref())
287+
.unwrap_or_else(|_| {
288+
panic!(
289+
"The human-readable part {} should never cause a failure",
290+
Self::HRP
291+
)
292+
})
293+
}
294+
281295
/// Decode an address from Bech32m encoding
282296
pub fn decode(string: impl AsRef<str>) -> Result<Self> {
283297
string_encoding::Format::decode(string)
@@ -720,6 +734,19 @@ mod tests {
720734
let bytes = address.serialize_to_vec();
721735
assert_eq!(bytes.len(), ESTABLISHED_ADDRESS_BYTES_LEN);
722736
}
737+
738+
#[test]
739+
fn test_compat_addr_decode_bech32(address in testing::arb_address()) {
740+
let encoded: String = address.encode();
741+
let encoded_compat: String = address.encode_compat();
742+
743+
assert_ne!(encoded, encoded_compat);
744+
745+
let decoded: Address = encoded.parse().unwrap();
746+
let decoded_compat: Address = encoded_compat.parse().unwrap();
747+
748+
assert_eq!(decoded, decoded_compat);
749+
}
723750
}
724751
}
725752

0 commit comments

Comments
 (0)