Skip to content

Commit 053ea34

Browse files
authored
Merge pull request #520 from Emurgo/evgenii/infromative_change_error
More informative error in add_change_if_needed
2 parents 28447c9 + 48f90fd commit 053ea34

File tree

4 files changed

+269
-0
lines changed

4 files changed

+269
-0
lines changed

rust/src/crypto.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crypto::bech32::Bech32 as _;
77
use rand_os::OsRng;
88
use std::io::{BufRead, Seek, Write};
99
use std::str::FromStr;
10+
use std::fmt::Display;
11+
use std::fmt;
1012

1113
use cryptoxide::blake2b::Blake2b;
1214

@@ -1058,6 +1060,12 @@ macro_rules! impl_hash_type {
10581060
String::is_referenceable()
10591061
}
10601062
}
1063+
1064+
impl Display for $name {
1065+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1066+
write!(f, "{}", self.to_hex())
1067+
}
1068+
}
10611069
};
10621070
}
10631071

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;
@@ -3153,6 +3155,12 @@ impl HeaderBody {
31533155
#[derive(Clone, Debug, Eq, PartialEq)]
31543156
pub struct AssetName(Vec<u8>);
31553157

3158+
impl Display for AssetName {
3159+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3160+
write!(f, "{}", hex::encode(&self.0))
3161+
}
3162+
}
3163+
31563164
impl Ord for AssetName {
31573165
fn cmp(&self, other: &Self) -> Ordering {
31583166
// 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
@@ -1302,6 +1302,11 @@ impl TransactionBuilder {
13021302
let input_total = self.get_total_input()?;
13031303
let output_total = self.get_total_output()?;
13041304

1305+
let shortage = get_input_shortage(&input_total, &output_total, &fee)?;
1306+
if let Some(shortage) = shortage {
1307+
return Err(JsError::from_str(&format!("Insufficient input in transaction. {}", shortage)));
1308+
}
1309+
13051310
use std::cmp::Ordering;
13061311
match &input_total.partial_cmp(&output_total.checked_add(&Value::new(&fee))?) {
13071312
Some(Ordering::Equal) => {
@@ -2926,6 +2931,196 @@ mod tests {
29262931
);
29272932
}
29282933

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

1706+
pub struct ValueShortage {
1707+
pub(crate) ada_shortage: Option<(Coin, Coin, Coin)>,
1708+
pub(crate) asset_shortage: Vec<(PolicyID, AssetName, Coin, Coin)>,
1709+
}
1710+
1711+
impl Display for ValueShortage {
1712+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1713+
write!(f, "shortage: {{")?;
1714+
if let Some((input_data, out_data, fee)) = self.ada_shortage {
1715+
writeln!(f, "ada in inputs: {}, ada in outputs: {}, fee {}", input_data, out_data, fee)?;
1716+
writeln!(f, "NOTE! \"ada in inputs\" must be >= (\"ada in outputs\" + fee) before adding change")?;
1717+
writeln!(f, "and \"ada in inputs\" must be == (\"ada in outputs\" + fee) after adding change")?;
1718+
}
1719+
for (policy_id, asset_name, asset_shortage, asset_available) in
1720+
&self.asset_shortage
1721+
{
1722+
write!(f, "policy id: \"{}\", asset name: \"{}\" ", policy_id, asset_name)?;
1723+
writeln!(f, "coins in inputs: {}, coins in outputs: {}", asset_shortage, asset_available)?;
1724+
}
1725+
write!(f, " }}")
1726+
}
1727+
}
1728+
1729+
pub(crate) fn get_input_shortage(all_inputs_value: &Value, all_outputs_value: &Value, fee: &Coin)
1730+
-> Result<Option<ValueShortage>, JsError> {
1731+
let mut shortage = ValueShortage{
1732+
ada_shortage: None,
1733+
asset_shortage: Vec::new()};
1734+
if all_inputs_value.coin < all_outputs_value.coin.checked_add(fee)? {
1735+
shortage.ada_shortage = Some((
1736+
all_inputs_value.coin.clone(),
1737+
all_outputs_value.coin.clone(),
1738+
fee.clone()));
1739+
}
1740+
1741+
if let Some(policies) = &all_outputs_value.multiasset {
1742+
for (policy_id, assets) in &policies.0 {
1743+
for (asset_name, coins) in &assets.0 {
1744+
let inputs_coins = match &all_inputs_value.multiasset {
1745+
Some(multiasset) => multiasset.get_asset(policy_id, asset_name),
1746+
None => Coin::zero()
1747+
};
1748+
1749+
if inputs_coins < *coins {
1750+
shortage.asset_shortage.push((policy_id.clone(), asset_name.clone(), inputs_coins, coins.clone()));
1751+
}
1752+
}
1753+
}
1754+
}
1755+
1756+
if shortage.ada_shortage.is_some() || shortage.asset_shortage.len() > 0 {
1757+
Ok(Some(shortage))
1758+
} else {
1759+
Ok(None)
1760+
}
1761+
}
1762+
17051763
#[cfg(test)]
17061764
mod tests {
17071765
use super::*;

0 commit comments

Comments
 (0)