Skip to content

Commit 7d1de45

Browse files
committed
feat(iota): make split-coin work with a single gas coin
1 parent 07e9f4f commit 7d1de45

File tree

2 files changed

+86
-5
lines changed

2 files changed

+86
-5
lines changed

crates/iota/src/client_commands.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,15 +1526,40 @@ impl IotaClientCommands {
15261526
match (amounts.as_ref(), count) {
15271527
(None, None) => bail!("You must use one of amounts or count options."),
15281528
(Some(_), Some(_)) => bail!("Cannot specify both amounts and count."),
1529-
(None, Some(0)) => bail!("Coin split count must be greater than 0"),
1529+
(None, Some(0)) | (None, Some(1)) => {
1530+
bail!("Coin split count must be greater than 1")
1531+
}
15301532
_ => { /*no_op*/ }
15311533
}
15321534
let client = context.get_client().await?;
1533-
let tx_kind = client
1534-
.transaction_builder()
1535-
.split_coin_tx_kind(coin_id, amounts, count)
1536-
.await?;
15371535
let signer = context.get_object_owner(&coin_id).await?;
1536+
let gas_coins_page = client
1537+
.coin_read_api()
1538+
.get_coins(signer, None, None, None)
1539+
.await?;
1540+
// If we only have a single coin, we have to split from the gas coin
1541+
let tx_kind = if gas_coins_page.data.len() == 1 {
1542+
if let Some(amounts) = amounts {
1543+
client
1544+
.transaction_builder()
1545+
.pay_iota_tx_kind(vec![signer; amounts.len()], amounts)?
1546+
} else {
1547+
let count_to_compute = count.expect("count is None");
1548+
let amount = gas_coins_page.data[0].balance / count_to_compute;
1549+
// Reduce by 1 as the gas coin is not included in the split
1550+
let count_to_split = count_to_compute.saturating_sub(1);
1551+
client.transaction_builder().pay_iota_tx_kind(
1552+
vec![signer; count_to_split as usize],
1553+
vec![amount; count_to_split as usize],
1554+
)?
1555+
}
1556+
} else {
1557+
client
1558+
.transaction_builder()
1559+
.split_coin_tx_kind(coin_id, amounts, count)
1560+
.await?
1561+
};
1562+
15381563
dry_run_or_execute_or_serialize(
15391564
signer, tx_kind, context, None, None, opts.gas, opts.rest,
15401565
)

crates/iota/tests/cli_tests.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2780,6 +2780,7 @@ async fn test_split_coin() -> Result<(), anyhow::Error> {
27802780
.await;
27812781
let rgp = test_cluster.get_reference_gas_price().await;
27822782
let address = test_cluster.get_address_0();
2783+
let address_1 = test_cluster.get_address_1();
27832784
let context = &mut test_cluster.wallet;
27842785
let client = context.get_client().await?;
27852786
let object_refs = client
@@ -2976,6 +2977,61 @@ async fn test_split_coin() -> Result<(), anyhow::Error> {
29762977
assert_eq!(get_gas_value(&updated_coin) + 1000 + 10, orig_value);
29772978
assert!((get_gas_value(&new_coins[0]) == 1000) || (get_gas_value(&new_coins[0]) == 10));
29782979
assert!((get_gas_value(&new_coins[1]) == 1000) || (get_gas_value(&new_coins[1]) == 10));
2980+
2981+
// Test with single gas coin
2982+
context.config_mut().set_active_address(Some(address_1));
2983+
let object_refs = client
2984+
.coin_read_api()
2985+
.get_coins(address_1, None, None, None)
2986+
.await?;
2987+
// First merge all coins so we only have a single one left
2988+
let resp = IotaClientCommands::PayAllIota {
2989+
input_coins: object_refs.data.iter().map(|o| o.coin_object_id).collect(),
2990+
recipient: KeyIdentity::Address(address_1),
2991+
opts: Opts::for_testing(rgp * TEST_ONLY_GAS_UNIT_FOR_TRANSFER),
2992+
}
2993+
.execute(context)
2994+
.await?;
2995+
if let IotaClientCommandResult::TransactionBlock(r) = resp {
2996+
assert!(r.status_ok().unwrap(), "Command PayAllIota failed: {r:?}");
2997+
} else {
2998+
panic!("Command PayAllIota failed")
2999+
};
3000+
3001+
let object_refs = client
3002+
.coin_read_api()
3003+
.get_coins(address_1, None, None, None)
3004+
.await?;
3005+
assert_eq!(object_refs.data.len(), 1, "More than one coin");
3006+
3007+
let gas = object_refs.data.first().unwrap().coin_object_id;
3008+
let resp = IotaClientCommands::SplitCoin {
3009+
opts: OptsWithGas::for_testing(None, rgp * TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN),
3010+
coin_id: gas,
3011+
amounts: Some(vec![10, 1000]),
3012+
count: None,
3013+
}
3014+
.execute(context)
3015+
.await?;
3016+
3017+
let new_coins = if let IotaClientCommandResult::TransactionBlock(r) = resp {
3018+
assert!(r.status_ok().unwrap(), "Command SplitCoin failed: {r:?}");
3019+
let effects = r.effects.as_ref().unwrap();
3020+
assert_eq!(effects.gas_object().object_id(), gas);
3021+
3022+
let new_object_refs = effects.created().to_vec();
3023+
let mut new_objects = Vec::with_capacity(new_object_refs.len());
3024+
for obj_ref in new_object_refs {
3025+
new_objects.push(
3026+
get_parsed_object_assert_existence(obj_ref.reference.object_id, context).await,
3027+
);
3028+
}
3029+
new_objects
3030+
} else {
3031+
panic!("Command SplitCoin failed")
3032+
};
3033+
assert!((get_gas_value(&new_coins[0]) == 10) || (get_gas_value(&new_coins[0]) == 1000));
3034+
assert!((get_gas_value(&new_coins[1]) == 1000) || (get_gas_value(&new_coins[1]) == 10));
29793035
Ok(())
29803036
}
29813037

0 commit comments

Comments
 (0)