Skip to content

Commit 9818300

Browse files
committed
add more information to add_change_if_needed errors
1 parent 8b0a2b2 commit 9818300

File tree

3 files changed

+261
-0
lines changed

3 files changed

+261
-0
lines changed

rust/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ use plutus::*;
6666
use schemars::JsonSchema;
6767
use std::cmp::Ordering;
6868
use std::collections::BTreeSet;
69+
use std::fmt::Display;
70+
use std::fmt;
6971
use utils::*;
7072

7173
type DeltaCoin = Int;
@@ -3256,6 +3258,12 @@ impl HeaderBody {
32563258
#[derive(Clone, Debug, Eq, PartialEq)]
32573259
pub struct AssetName(Vec<u8>);
32583260

3261+
impl Display for AssetName {
3262+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3263+
write!(f, "{}", hex::encode(&self.0))
3264+
}
3265+
}
3266+
32593267
impl Ord for AssetName {
32603268
fn cmp(&self, other: &Self) -> Ordering {
32613269
// Implementing canonical CBOR order for asset names,

rust/src/tx_builder.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,11 @@ impl TransactionBuilder {
12971297
let input_total = self.get_total_input()?;
12981298
let output_total = self.get_total_output()?;
12991299

1300+
let shortage = get_input_shortage(&input_total, &output_total, &fee)?;
1301+
if let Some(shortage) = shortage {
1302+
return Err(JsError::from_str(&format!("Insufficient input in transaction. {}", shortage)));
1303+
}
1304+
13001305
use std::cmp::Ordering;
13011306
match &input_total.partial_cmp(&output_total.checked_add(&Value::new(&fee))?) {
13021307
Some(Ordering::Equal) => {
@@ -2921,6 +2926,196 @@ mod tests {
29212926
);
29222927
}
29232928

2929+
#[test]
2930+
fn change_with_input_and_mint_not_enough_ada() {
2931+
let mut tx_builder = create_tx_builder_with_fee(&create_linear_fee(1, 1));
2932+
let spend = root_key_15()
2933+
.derive(harden(1852))
2934+
.derive(harden(1815))
2935+
.derive(harden(0))
2936+
.derive(0)
2937+
.derive(0)
2938+
.to_public();
2939+
let change_key = root_key_15()
2940+
.derive(harden(1852))
2941+
.derive(harden(1815))
2942+
.derive(harden(0))
2943+
.derive(1)
2944+
.derive(0)
2945+
.to_public();
2946+
let stake = root_key_15()
2947+
.derive(harden(1852))
2948+
.derive(harden(1815))
2949+
.derive(harden(0))
2950+
.derive(2)
2951+
.derive(0)
2952+
.to_public();
2953+
2954+
let spend_cred = StakeCredential::from_keyhash(&spend.to_raw_key().hash());
2955+
let stake_cred = StakeCredential::from_keyhash(&stake.to_raw_key().hash());
2956+
2957+
let (min_script, policy_id) = mint_script_and_policy(0);
2958+
let asset_name = AssetName::new(vec![0u8, 1, 2, 3]).unwrap();
2959+
2960+
let amount_minted = to_bignum(1000);
2961+
let amount_sent = to_bignum(500);
2962+
let amount_input_amount = to_bignum(600);
2963+
2964+
let mut asset_input = Assets::new();
2965+
asset_input.insert(&asset_name, &amount_input_amount);
2966+
let mut mass_input = MultiAsset::new();
2967+
mass_input.insert(&policy_id, &asset_input);
2968+
2969+
// Input with 600 coins
2970+
tx_builder.add_input(
2971+
&EnterpriseAddress::new(NetworkInfo::testnet().network_id(), &spend_cred).to_address(),
2972+
&TransactionInput::new(&genesis_id(), 0),
2973+
&Value::new(&to_bignum(600)),
2974+
);
2975+
2976+
tx_builder.add_input(
2977+
&EnterpriseAddress::new(NetworkInfo::testnet().network_id(), &spend_cred).to_address(),
2978+
&TransactionInput::new(&genesis_id(), 1),
2979+
&Value::new_with_assets(&to_bignum(1), &mass_input),
2980+
);
2981+
2982+
let addr_net_0 = BaseAddress::new(
2983+
NetworkInfo::testnet().network_id(),
2984+
&spend_cred,
2985+
&stake_cred,
2986+
).to_address();
2987+
2988+
// Adding mint of the asset - which should work as an input
2989+
tx_builder.add_mint_asset(&min_script, &asset_name, Int::new(&amount_minted));
2990+
2991+
let mut asset = Assets::new();
2992+
asset.insert(&asset_name, &amount_sent);
2993+
let mut mass = MultiAsset::new();
2994+
mass.insert(&policy_id, &asset);
2995+
2996+
// One coin and the minted asset goes into the output
2997+
let mut output_amount = Value::new(&to_bignum(400));
2998+
output_amount.set_multiasset(&mass);
2999+
3000+
tx_builder
3001+
.add_output(
3002+
&TransactionOutputBuilder::new()
3003+
.with_address(&addr_net_0)
3004+
.next()
3005+
.unwrap()
3006+
.with_value(&output_amount)
3007+
.build()
3008+
.unwrap(),
3009+
)
3010+
.unwrap();
3011+
3012+
let change_cred = StakeCredential::from_keyhash(&change_key.to_raw_key().hash());
3013+
let change_addr = BaseAddress::new(
3014+
NetworkInfo::testnet().network_id(),
3015+
&change_cred,
3016+
&stake_cred,
3017+
)
3018+
.to_address();
3019+
3020+
let added_change = tx_builder.add_change_if_needed(&change_addr);
3021+
assert!(added_change.is_err());
3022+
}
3023+
3024+
#[test]
3025+
fn change_with_input_and_mint_not_enough_assets() {
3026+
let mut tx_builder = create_tx_builder_with_fee(&create_linear_fee(1, 1));
3027+
let spend = root_key_15()
3028+
.derive(harden(1852))
3029+
.derive(harden(1815))
3030+
.derive(harden(0))
3031+
.derive(0)
3032+
.derive(0)
3033+
.to_public();
3034+
let change_key = root_key_15()
3035+
.derive(harden(1852))
3036+
.derive(harden(1815))
3037+
.derive(harden(0))
3038+
.derive(1)
3039+
.derive(0)
3040+
.to_public();
3041+
let stake = root_key_15()
3042+
.derive(harden(1852))
3043+
.derive(harden(1815))
3044+
.derive(harden(0))
3045+
.derive(2)
3046+
.derive(0)
3047+
.to_public();
3048+
3049+
let spend_cred = StakeCredential::from_keyhash(&spend.to_raw_key().hash());
3050+
let stake_cred = StakeCredential::from_keyhash(&stake.to_raw_key().hash());
3051+
3052+
let (min_script, policy_id) = mint_script_and_policy(0);
3053+
let asset_name = AssetName::new(vec![0u8, 1, 2, 3]).unwrap();
3054+
3055+
let amount_minted = to_bignum(1000);
3056+
let amount_sent = to_bignum(100000);
3057+
let amount_input_amount = to_bignum(600);
3058+
3059+
let mut asset_input = Assets::new();
3060+
asset_input.insert(&asset_name, &amount_input_amount);
3061+
let mut mass_input = MultiAsset::new();
3062+
mass_input.insert(&policy_id, &asset_input);
3063+
3064+
// Input with 600 coins
3065+
tx_builder.add_input(
3066+
&EnterpriseAddress::new(NetworkInfo::testnet().network_id(), &spend_cred).to_address(),
3067+
&TransactionInput::new(&genesis_id(), 0),
3068+
&Value::new(&to_bignum(100000)),
3069+
);
3070+
3071+
tx_builder.add_input(
3072+
&EnterpriseAddress::new(NetworkInfo::testnet().network_id(), &spend_cred).to_address(),
3073+
&TransactionInput::new(&genesis_id(), 1),
3074+
&Value::new_with_assets(&to_bignum(1), &mass_input),
3075+
);
3076+
3077+
let addr_net_0 = BaseAddress::new(
3078+
NetworkInfo::testnet().network_id(),
3079+
&spend_cred,
3080+
&stake_cred,
3081+
).to_address();
3082+
3083+
// Adding mint of the asset - which should work as an input
3084+
tx_builder.add_mint_asset(&min_script, &asset_name, Int::new(&amount_minted));
3085+
3086+
let mut asset = Assets::new();
3087+
asset.insert(&asset_name, &amount_sent);
3088+
let mut mass = MultiAsset::new();
3089+
mass.insert(&policy_id, &asset);
3090+
3091+
// One coin and the minted asset goes into the output
3092+
let mut output_amount = Value::new(&to_bignum(400));
3093+
output_amount.set_multiasset(&mass);
3094+
3095+
tx_builder
3096+
.add_output(
3097+
&TransactionOutputBuilder::new()
3098+
.with_address(&addr_net_0)
3099+
.next()
3100+
.unwrap()
3101+
.with_value(&output_amount)
3102+
.build()
3103+
.unwrap(),
3104+
)
3105+
.unwrap();
3106+
3107+
let change_cred = StakeCredential::from_keyhash(&change_key.to_raw_key().hash());
3108+
let change_addr = BaseAddress::new(
3109+
NetworkInfo::testnet().network_id(),
3110+
&change_cred,
3111+
&stake_cred,
3112+
)
3113+
.to_address();
3114+
3115+
let added_change = tx_builder.add_change_if_needed(&change_addr);
3116+
assert!(added_change.is_err());
3117+
}
3118+
29243119
#[ignore]
29253120
#[test]
29263121
fn build_tx_with_native_assets_change() {

rust/src/utils.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::{
1313
io::{BufRead, Seek, Write},
1414
ops::{Rem, Sub},
1515
};
16+
use std::fmt::Display;
1617
use itertools::Itertools;
1718

1819
use super::*;
@@ -1726,6 +1727,63 @@ pub(crate) fn opt64<T>(o: &Option<T>) -> u64 {
17261727
o.is_some() as u64
17271728
}
17281729

1730+
pub struct ValueShortage {
1731+
pub(crate) ada_shortage: Option<(Coin, Coin, Coin)>,
1732+
pub(crate) asset_shortage: Vec<(PolicyID, AssetName, Coin, Coin)>,
1733+
}
1734+
1735+
impl Display for ValueShortage {
1736+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1737+
write!(f, "shortage: {{")?;
1738+
if let Some((input_data, out_data, fee)) = self.ada_shortage {
1739+
writeln!(f, "ada in inputs: {}, ada in outputs: {}, fee {}", input_data, out_data, fee)?;
1740+
writeln!(f, "NOTE! \"ada in inputs\" must be >= (\"ada in outputs\" + fee) before adding change")?;
1741+
writeln!(f, "and \"ada in inputs\" must be == (\"ada in outputs\" + fee) after adding change")?;
1742+
}
1743+
for (policy_id, asset_name, asset_shortage, asset_available) in
1744+
&self.asset_shortage
1745+
{
1746+
write!(f, "policy id: \"{}\", asset name: \"{}\" ", policy_id, asset_name)?;
1747+
writeln!(f, "coins in inputs: {}, coins in outputs: {}", asset_shortage, asset_available)?;
1748+
}
1749+
write!(f, " }}")
1750+
}
1751+
}
1752+
1753+
pub(crate) fn get_input_shortage(all_inputs_value: &Value, all_outputs_value: &Value, fee: &Coin)
1754+
-> Result<Option<ValueShortage>, JsError> {
1755+
let mut shortage = ValueShortage{
1756+
ada_shortage: None,
1757+
asset_shortage: Vec::new()};
1758+
if all_inputs_value.coin < all_outputs_value.coin.checked_add(fee)? {
1759+
shortage.ada_shortage = Some((
1760+
all_inputs_value.coin.clone(),
1761+
all_outputs_value.coin.clone(),
1762+
fee.clone()));
1763+
}
1764+
1765+
if let Some(policies) = &all_outputs_value.multiasset {
1766+
for (policy_id, assets) in &policies.0 {
1767+
for (asset_name, coins) in &assets.0 {
1768+
let inputs_coins = match &all_inputs_value.multiasset {
1769+
Some(multiasset) => multiasset.get_asset(policy_id, asset_name),
1770+
None => Coin::zero()
1771+
};
1772+
1773+
if inputs_coins < *coins {
1774+
shortage.asset_shortage.push((policy_id.clone(), asset_name.clone(), inputs_coins, coins.clone()));
1775+
}
1776+
}
1777+
}
1778+
}
1779+
1780+
if shortage.ada_shortage.is_some() || shortage.asset_shortage.len() > 0 {
1781+
Ok(Some(shortage))
1782+
} else {
1783+
Ok(None)
1784+
}
1785+
}
1786+
17291787
#[cfg(test)]
17301788
mod tests {
17311789
use super::*;

0 commit comments

Comments
 (0)