Skip to content

Commit

Permalink
fix issue with internal payments
Browse files Browse the repository at this point in the history
  • Loading branch information
OlofBlomqvist committed Jul 30, 2023
1 parent 2beaade commit 6d20355
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/lib/deserialization/marlowe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::parsing::{marlowe::{ParseError, ContractParseResult}, MarloweParser};

/// Parses a string into an instance of a Marlowe contract
pub fn deserialize(content:&str) -> Result<ContractParseResult,ParseError> {
deserialize_with_input(content,Default::default())
deserialize_with_input(content.trim_start().trim_end(),Default::default())
}

pub fn deserialize_with_input(content:&str,input:HashMap<String,i64>) -> Result<ContractParseResult,ParseError> {
Expand Down
8 changes: 4 additions & 4 deletions src/lib/extras/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,7 @@ pub struct WasmInputDeposit {
#[wasm_bindgen::prelude::wasm_bindgen(getter_with_clone)] pub who_is_expected_to_pay:WasmParty,
#[wasm_bindgen::prelude::wasm_bindgen(getter_with_clone)] pub expected_asset_type: WasmToken,
#[wasm_bindgen::prelude::wasm_bindgen(getter_with_clone)] pub expected_amount: i64,
#[wasm_bindgen::prelude::wasm_bindgen(getter_with_clone)] pub expected_target_account:WasmPayee,
#[wasm_bindgen::prelude::wasm_bindgen(getter_with_clone)] pub expected_payee:WasmPayee,
#[wasm_bindgen::prelude::wasm_bindgen(getter_with_clone)] pub continuation_dsl: String
}

Expand Down Expand Up @@ -1066,15 +1066,15 @@ impl TryFrom<crate::semantics::MachineState> for WasmMachineState {
who_is_expected_to_pay,
expected_asset_type,
expected_amount,
expected_target_account,
expected_payee,
continuation
} => {
let dep = WasmInputDeposit {
who_is_expected_to_pay: who_is_expected_to_pay.clone().try_into().unwrap(),
expected_asset_type: WasmToken { name: expected_asset_type.token_name.to_string(), pol: expected_asset_type.currency_symbol.to_string() },
expected_amount: *expected_amount as i64,
expected_target_account: {
match expected_target_account {
expected_payee: {
match expected_payee {
AccountId::Address(a) => WasmPayee { typ: WasmPayeeType::AccountAddress, val: a.as_bech32().unwrap() },
AccountId::Role { role_token } => WasmPayee { typ: WasmPayeeType::AccountRole, val: role_token.to_string() },
}
Expand Down
61 changes: 49 additions & 12 deletions src/lib/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub enum InputType {
who_is_expected_to_pay:Party ,
expected_asset_type: Token,
expected_amount: i64,
expected_target_account:crate::types::marlowe::AccountId,
expected_payee:crate::types::marlowe::AccountId,
continuation: Contract
},

Expand Down Expand Up @@ -128,7 +128,18 @@ pub trait ContractSemantics<T> {

}

impl ContractInstance {
impl<'a> ContractInstance {

pub fn is_closed(&self) -> bool {
match &self.process().unwrap().1 {
MachineState::Closed => true,
_ => false
}
}

pub fn locked_amounts(&'a self) -> Vec<(&'a Party, &'a Token, u64)> {
self.state.locked_amounts()
}

pub fn from_datum_cborhex(datum_cbor_hex:&str) -> Result<Self,String> {
let bytes = hex::decode(datum_cbor_hex).map_err(|_|String::from("failed to decode datum from cbor-hex."))?;
Expand Down Expand Up @@ -189,6 +200,7 @@ impl ContractInstance {
}
}


}

impl ContractSemantics<ContractInstance> for ContractInstance {
Expand Down Expand Up @@ -507,7 +519,7 @@ impl ContractSemantics<ContractInstance> for ContractInstance {
who_is_expected_to_pay,
expected_asset_type,
expected_amount,
expected_target_account,
expected_payee: expected_target_account,
continuation
} => {

Expand Down Expand Up @@ -595,7 +607,7 @@ impl ContractSemantics<ContractInstance> for ContractInstance {

Contract::Pay {
from_account:Some(from),
to:Some(to_acc),
to:Some(payee),
token:Some(tok),
pay:Some(pay_amount),
then:Some(continuation)
Expand All @@ -615,27 +627,52 @@ impl ContractSemantics<ContractInstance> for ContractInstance {

let reduced_amount = self.eval_num_value(pay_amount)?;

let reduced_known_positive_amount : u64 = match reduced_amount.try_into() {
let reduced_known_positive_amount_to_pay : u64 = match reduced_amount.try_into() {
Ok(v) => v,
Err(e) => return Err(format!("Unable to process payment due to the expected payment amount being negative! {:?}",e))
};

if let Some(available_amount) = acc_val {

if *available_amount < reduced_known_positive_amount {
return Err(format!("Unable to perform payment as the account ({from}) does not have enough tokens ({tok}). Contract attempted to send '{reduced_known_positive_amount}' when the account only contains '{available_amount}'."))
if *available_amount < reduced_known_positive_amount_to_pay {
return Err(format!("Unable to perform payment as the account ({from}) does not have enough tokens ({tok}). Contract attempted to send '{reduced_known_positive_amount_to_pay}' when the account only contains '{available_amount}'."))
} else {
let new_amount: u64 = *available_amount - reduced_known_positive_amount;
let new_amount: u64 = *available_amount - reduced_known_positive_amount_to_pay;

*available_amount = new_amount;
}

new_instance.logs.push(format!("A mayment was made! '{from}' sent '{reduced_known_positive_amount}' of '{tok}' from '{from}' to: '{to_acc}'"));
// update payee account status
match payee {
Payee::Account(Some(internal_payee)) => {
let internal_payee_acc_val_amount = new_instance.state.accounts.get_mut(&(internal_payee.clone(),tok.clone()));
match internal_payee_acc_val_amount {
Some(payee_acc_tok_val) => {
// this payment adds value to an internal account that already holds some amount of this token
// so we just increment the existing amount
*payee_acc_tok_val = *payee_acc_tok_val + reduced_known_positive_amount_to_pay;
},
None => {
// the payment goes to an internal account that does not have any amount of the token
// so we will need to add it to the state
new_instance.state.accounts.insert((internal_payee.clone(),tok.clone()), reduced_known_positive_amount_to_pay);
},
}
},
Payee::Party(Some(_exteral_payee)) => {
// the contract sends a payment to some external address or role token holder,
// so we dont need to update any internal account state for the target/payee here.
},
_ => return Err(format!("There is an issue with this contract. The payee is not specified."))
};


new_instance.logs.push(format!("A mayment was made! '{from}' sent '{reduced_known_positive_amount_to_pay}' of '{tok}' to: '{payee}'"));
new_instance.payments.push(Payment {
payment_from: from.clone(),
to: to_acc.clone(),
to: payee.clone(),
token: tok.clone(),
amount: reduced_known_positive_amount,
amount: reduced_known_positive_amount_to_pay,
});
Ok((new_instance,MachineState::ReadyForNextStep))
} else {
Expand Down Expand Up @@ -747,7 +784,7 @@ impl ContractSemantics<ContractInstance> for ContractInstance {
who_is_expected_to_pay: from.clone(),
expected_asset_type: tok.clone(),
expected_amount: v,
expected_target_account: to.clone(),
expected_payee: to.clone(),
continuation: *(continuation.clone())
})
} ,
Expand Down
137 changes: 135 additions & 2 deletions src/lib/tests/semantics.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::num::TryFromIntError;

use crate::{semantics::{ContractInstance, MachineState, ContractSemantics}, types::marlowe::*};
use crate::{semantics::{ContractInstance, MachineState, ContractSemantics}, types::marlowe::{*, self}};


#[test]
Expand Down Expand Up @@ -464,7 +464,7 @@ fn negative_deposits_are_treated_as_zero() {
who_is_expected_to_pay:_,
expected_asset_type:_,
expected_amount,
expected_target_account:_,
expected_payee:_,
continuation :_
} = expected.first().unwrap() {

Expand Down Expand Up @@ -497,4 +497,137 @@ fn negative_deposits_are_treated_as_zero() {
_ => panic!("expected this contract to be waiting for input, but current state is: {state:?}")
}

}



#[test]
fn pay_to_external_payee() {

let contract_dsl = r#"
When
[Case
(Deposit
(Role "the bank")
(Role "the bank")
(Token "" "")
(Constant 5000000)
)
(Pay
(Role "the bank")
(Party (Role "some external role token holder"))
(Token "" "")
(Constant 5000000)
Close
)]
1753881840000 Close
"#;

let deserialization_result = crate::deserialization::marlowe::deserialize(contract_dsl).expect("should be able to deserialize test contract");

assert!(deserialization_result.parties.len() == 2);

let (instance,_state) =
ContractInstance::new(&deserialization_result.contract, None)
.process()
.expect("should be able to process test contract prior to applying inputs");

let the_bank = marlowe::Party::role("the bank");
let the_payee = marlowe::Payee::Party(Some(Party::role("some external role token holder")));

// bank sends 5m ada to their internal contract account
let (instance,state) =
instance.apply_input_deposit(the_bank.clone(), Token::ada(), 5_000_000, the_bank.clone()).expect("should be able to apply deposit")
.process()
.expect("should be able to process contract after applying deposit input");

match state {
MachineState::Closed => {},
_ => panic!("contract should have closed, but is actually in state: {state:?}")
}

// bank should have an empty acc
assert!(instance.state.accounts.len() == 1);

// a single payment should have been made to the_payee from the_bank
assert!(instance.payments.len() == 1);
let payment = instance.payments.first().unwrap();
assert!(payment.amount == 5_000_000);
assert!(payment.token == Token::ada());
assert!(payment.to == the_payee);
assert!(payment.payment_from == the_bank);

// the only acc in the contract should now have no tokens as it was sent to an external payee
let bank_acc = instance.state.accounts.get(&(the_bank,Token::ada())).expect("bank should have account with zero amount");
assert!(bank_acc == &0)


}



#[test]
fn pay_to_internal_payee() {

let contract_dsl = r#"
When
[Case
(Deposit
(Role "the bank")
(Role "the bank")
(Token "" "")
(Constant 5000000)
)
(Pay
(Role "the bank")
(Account (Role "some internal acc"))
(Token "" "")
(Constant 5000000)
Close
)]
1753881840000 Close
"#;

let deserialization_result = crate::deserialization::marlowe::deserialize(contract_dsl).expect("should be able to deserialize test contract");

assert!(deserialization_result.parties.len() == 2);

let (instance,_state) =
ContractInstance::new(&deserialization_result.contract, None)
.process()
.expect("should be able to process test contract prior to applying inputs");

let the_bank = marlowe::Party::role("the bank");
let the_payee = marlowe::Payee::Account(Some(Party::role("some internal acc")));

// bank sends 5m ada to their internal contract account
let (instance,state) =
instance.apply_input_deposit(the_bank.clone(), Token::ada(), 5_000_000, the_bank.clone()).expect("should be able to apply deposit")
.process()
.expect("should be able to process contract after applying deposit input");

match state {
MachineState::Closed => {},
_ => panic!("contract should have closed, but is actually in state: {state:?}")
}

// bank should have an empty acc
assert!(instance.state.accounts.len() == 2);

// a single payment should have been made to the_payee from the_bank
assert!(instance.payments.len() == 1);
let payment = instance.payments.first().unwrap();
assert!(payment.amount == 5_000_000);
assert!(payment.token == Token::ada());
assert!(payment.to == the_payee);
assert!(payment.payment_from == the_bank);

// the bank acc in the contract should now have no tokens as it was sent to another payee account
let bank_acc = instance.state.accounts.get(&(the_bank,Token::ada())).expect("bank should have account with zero amount");
assert!(bank_acc == &0);

let payee_acc = instance.state.accounts.get(&(Party::role("some internal acc"),Token::ada())).expect("payee should have ada in their account");
assert!(payee_acc == &5_000_000)


}
10 changes: 10 additions & 0 deletions src/lib/types/marlowe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,16 @@ pub struct State {
pub min_time : u64 , // POSIXTime
}

impl<'a> State {
pub fn locked_amounts(&'a self) -> Vec<(&'a Party,&'a Token,u64)> {
let mut items : Vec<(&'a Party,&'a Token,u64)> = vec![];
for ((party,token),amount) in &self.accounts {
items.push((party,token,*amount));
}
items
}
}



impl TryFrom<Party> for crate::types::marlowe_strict::Party {
Expand Down

0 comments on commit 6d20355

Please sign in to comment.