Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameterize CML Valid Range Calculation #147

Merged
merged 5 commits into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 9 additions & 19 deletions sample-dApps/checking_account/src/endpoints/pull.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,24 @@ pub async fn pull_from_account<LC: LedgerClient<CheckingAccountDatums, ()>>(
checking_account_output_id: OutputId,
amount: u64,
) -> SCLogicResult<TxActions<CheckingAccountDatums, ()>> {
let network = ledger_client
.network()
.await
.map_err(|e| SCLogicError::Endpoint(Box::new(e)))?;
let allow_pull_validator = pull_validator().map_err(SCLogicError::ValidatorScript)?;
let allow_pull_address = allow_pull_validator
.address(network)
.map_err(SCLogicError::ValidatorScript)?;
let network = ledger_client.network().await?;
let allow_pull_validator = pull_validator()?;
let allow_pull_address = allow_pull_validator.address(network)?;
let allow_pull_output = ledger_client
.all_outputs_at_address(&allow_pull_address)
.await
.map_err(|e| SCLogicError::Lookup(Box::new(e)))?
.await?
.into_iter()
.find(|o| o.id() == &allow_pull_output_id)
.ok_or(CheckingAccountError::OutputNotFound(
allow_pull_output_id.clone(),
))
.map_err(|e| SCLogicError::Endpoint(Box::new(e)))?;

let validator =
checking_account_validator().map_err(|e| SCLogicError::Endpoint(Box::new(e)))?;
let checking_account_address = validator
.address(network)
.map_err(SCLogicError::ValidatorScript)?;
let validator = checking_account_validator()?;
let checking_account_address = validator.address(network)?;
let checking_account_output = ledger_client
.all_outputs_at_address(&checking_account_address)
.await
.map_err(|e| SCLogicError::Lookup(Box::new(e)))?
.await?
.into_iter()
.find(|o| o.id() == &checking_account_output_id)
.ok_or(CheckingAccountError::OutputNotFound(
Expand Down Expand Up @@ -85,7 +75,7 @@ pub async fn pull_from_account<LC: LedgerClient<CheckingAccountDatums, ()>>(
};

let current_time = ledger_client
.current_time_posix_milliseconds()
.current_time_secs()
.await
.map_err(|e| SCLogicError::Endpoint(Box::new(e)))?;

Expand Down Expand Up @@ -131,6 +121,6 @@ pub async fn pull_from_account<LC: LedgerClient<CheckingAccountDatums, ()>>(
new_account_value,
checking_account_address,
)
.with_valid_range(current_time.into(), None);
.with_valid_range_secs(Some(current_time / 1000), None);
Ok(actions)
}
226 changes: 188 additions & 38 deletions sample-dApps/time-locked-contract/src/logic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::logic::script::get_script;
use async_trait::async_trait;
use naumachia::output::DatumKind;
use naumachia::{
address::PolicyId,
ledger_client::LedgerClient,
Expand All @@ -17,7 +18,7 @@ pub mod script;
pub struct TimeLockedLogic;

pub enum TimeLockedEndpoints {
Lock { amount: u64, timestamp: i64 },
Lock { amount: u64, after_secs: i64 },
Claim { output_id: OutputId },
}

Expand All @@ -33,6 +34,8 @@ pub enum TimeLockedLookupResponses {
pub enum TimeLockedError {
#[error("Could not find an output with id: {0:?}")]
OutputNotFound(OutputId),
#[error("Could not find a datum in output: {0:?}")]
DatumUnreadable(OutputId),
}

#[async_trait]
Expand All @@ -48,9 +51,10 @@ impl SCLogic for TimeLockedLogic {
ledger_client: &LC,
) -> SCLogicResult<TxActions<Self::Datums, Self::Redeemers>> {
match endpoint {
TimeLockedEndpoints::Lock { amount, timestamp } => {
impl_lock(ledger_client, amount, timestamp).await
}
TimeLockedEndpoints::Lock {
amount,
after_secs: timestamp,
} => impl_lock(ledger_client, amount, timestamp).await,
TimeLockedEndpoints::Claim { output_id } => impl_claim(ledger_client, output_id).await,
}
}
Expand All @@ -70,67 +74,213 @@ impl SCLogic for TimeLockedLogic {
async fn impl_lock<LC: LedgerClient<i64, ()>>(
ledger_client: &LC,
amount: u64,
timestamp: i64,
after_secs: i64,
) -> SCLogicResult<TxActions<i64, ()>> {
let network = ledger_client
.network()
.await
.map_err(|e| SCLogicError::Endpoint(Box::new(e)))?;
let network = ledger_client.network().await?;
let mut values = Values::default();
values.add_one_value(&PolicyId::Lovelace, amount);
let script = get_script().map_err(SCLogicError::ValidatorScript)?;
let address = script
.address(network)
.map_err(SCLogicError::ValidatorScript)?;
let tx_actions = TxActions::v2().with_script_init(timestamp, values, address);
let script = get_script()?;
let address = script.address(network)?;
let current_time = ledger_client.current_time_secs().await?;
let lock_timestamp = (current_time + after_secs) * 1000;
let tx_actions = TxActions::v2().with_script_init(lock_timestamp, values, address);
Ok(tx_actions)
}

async fn impl_claim<LC: LedgerClient<i64, ()>>(
ledger_client: &LC,
output_id: OutputId,
) -> SCLogicResult<TxActions<i64, ()>> {
let network = ledger_client
.network()
.await
.map_err(|e| SCLogicError::Endpoint(Box::new(e)))?;
let script = get_script().map_err(SCLogicError::ValidatorScript)?;
let address = script
.address(network)
.map_err(SCLogicError::ValidatorScript)?;
let network = ledger_client.network().await?;
let script = get_script()?;
let address = script.address(network)?;
let output = ledger_client
.all_outputs_at_address(&address)
.await
.map_err(|e| SCLogicError::Lookup(Box::new(e)))?
.await?
.into_iter()
.find(|o| o.id() == &output_id)
.ok_or(TimeLockedError::OutputNotFound(output_id))
.ok_or(TimeLockedError::OutputNotFound(output_id.clone()))
.map_err(|e| SCLogicError::Endpoint(Box::new(e)))?;
let redeemer = ();
let script_box = Box::new(script);
let lower_bound = if let DatumKind::Typed(inner) = output.datum().clone() {
inner / 1000
} else {
return Err(SCLogicError::Endpoint(Box::new(
TimeLockedError::OutputNotFound(output_id),
)));
};
let tx_actions = TxActions::v2()
.with_script_redeem(output, redeemer, script_box)
.with_valid_range(Some(1595967616), None);
.with_valid_range_secs(Some(lower_bound), None);
Ok(tx_actions)
}

async fn impl_list_active_contracts<LC: LedgerClient<i64, ()>>(
ledger_client: &LC,
count: usize,
) -> SCLogicResult<TimeLockedLookupResponses> {
let network = ledger_client
.network()
.await
.map_err(|e| SCLogicError::Endpoint(Box::new(e)))?;
let script = get_script().map_err(SCLogicError::ValidatorScript)?;
let address = script
.address(network)
.map_err(SCLogicError::ValidatorScript)?;
let outputs = ledger_client
.outputs_at_address(&address, count)
.await
.map_err(|e| SCLogicError::Lookup(Box::new(e)))?;
let network = ledger_client.network().await?;
let script = get_script()?;
let address = script.address(network)?;
let outputs = ledger_client.outputs_at_address(&address, count).await?;
let subset = outputs.into_iter().take(count).collect();
let res = TimeLockedLookupResponses::ActiveContracts(subset);
Ok(res)
}

#[cfg(test)]
mod tests {
#![allow(non_snake_case)]

use super::*;
use naumachia::{
error::Error,
ledger_client::test_ledger_client::TestBackendsBuilder,
ledger_client::LedgerClientError,
smart_contract::{SmartContract, SmartContractTrait},
Address, Network,
};

#[tokio::test]
async fn lock__creates_new_instance() {
// given
let me = Address::from_bech32("addr_test1qpmtp5t0t5y6cqkaz7rfsyrx7mld77kpvksgkwm0p7en7qum7a589n30e80tclzrrnj8qr4qvzj6al0vpgtnmrkkksnqd8upj0").unwrap();
let start_amount = 100_000_000;
let start_time = 1;
let backend = TestBackendsBuilder::new(&me)
.with_starting_time(start_time)
.start_output(&me)
.with_value(PolicyId::Lovelace, start_amount)
.finish_output()
.build_in_memory();

let amount = 10_000_000;
let after_secs = 2;

let endpoint = TimeLockedEndpoints::Lock { amount, after_secs };

let contract = SmartContract::new(&TimeLockedLogic, &backend);

// when
contract.hit_endpoint(endpoint).await.unwrap();

// then
let network = backend.ledger_client().network().await.unwrap();
let script_address = get_script().unwrap().address(network).unwrap();
let outputs = backend
.ledger_client()
.all_outputs_at_address(&script_address)
.await
.unwrap();

let output = outputs.first().unwrap();

let value = output.values().get(&PolicyId::Lovelace).unwrap();
assert_eq!(value, amount);
let datum = output.datum().clone().unwrap_typed();
assert_eq!(datum, (start_time + after_secs) * 1000);
}

#[tokio::test]
async fn claim__can_claim_output_within_time_range() {
// given
let me = Address::from_bech32("addr_test1qpmtp5t0t5y6cqkaz7rfsyrx7mld77kpvksgkwm0p7en7qum7a589n30e80tclzrrnj8qr4qvzj6al0vpgtnmrkkksnqd8upj0").unwrap();
let start_amount = 100_000_000;
let start_time = 10_000;

let script_address = get_script().unwrap().address(Network::Testnet).unwrap();
let locked_amount = 10_000_000;
let datum = 5_000;

let backend = TestBackendsBuilder::new(&me)
.with_starting_time(start_time)
.start_output(&me)
.with_value(PolicyId::Lovelace, start_amount)
.finish_output()
.start_output(&script_address)
.with_value(PolicyId::Lovelace, locked_amount)
.with_datum(datum)
.finish_output()
.build_in_memory();

let endpoint = TimeLockedEndpoints::Claim {
output_id: backend
.ledger_client()
.outputs_at_address(&script_address, 1)
.await
.unwrap()
.first()
.unwrap()
.id()
.clone(),
};

let contract = SmartContract::new(&TimeLockedLogic, &backend);

// when
contract.hit_endpoint(endpoint).await.unwrap();

// then
let outputs = backend
.ledger_client()
.all_outputs_at_address(&script_address)
.await
.unwrap();
assert!(outputs.is_empty());

let my_balance = backend
.ledger_client()
.balance_at_address(&me, &PolicyId::Lovelace)
.await
.unwrap();
assert_eq!(my_balance, start_amount + locked_amount);
}

#[tokio::test]
async fn claim__fails_if_out_of_range() {
// given
let me = Address::from_bech32("addr_test1qpmtp5t0t5y6cqkaz7rfsyrx7mld77kpvksgkwm0p7en7qum7a589n30e80tclzrrnj8qr4qvzj6al0vpgtnmrkkksnqd8upj0").unwrap();
let start_amount = 100_000_000;
let start_time = 10_000;

let script_address = get_script().unwrap().address(Network::Testnet).unwrap();
let locked_amount = 10_000_000;
let datum = 15_000;

let backend = TestBackendsBuilder::new(&me)
.with_starting_time(start_time)
.start_output(&me)
.with_value(PolicyId::Lovelace, start_amount)
.finish_output()
.start_output(&script_address)
.with_value(PolicyId::Lovelace, locked_amount)
.with_datum(datum)
.finish_output()
.build_in_memory();

let endpoint = TimeLockedEndpoints::Claim {
output_id: backend
.ledger_client()
.outputs_at_address(&script_address, 1)
.await
.unwrap()
.first()
.unwrap()
.id()
.clone(),
};

let contract = SmartContract::new(&TimeLockedLogic, &backend);

// when
let res = contract.hit_endpoint(endpoint).await;

// then
let err = res.unwrap_err();
assert!(matches!(
err,
Error::LedgerClient(LedgerClientError::FailedToIssueTx(_))
));
}
}
19 changes: 12 additions & 7 deletions sample-dApps/time-locked-contract/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ struct Args {
#[derive(clap::Subcommand, Debug)]
enum ActionParams {
/// Lock amount at script address
// TODO: Add time stamp
Lock { amount: f64 },
Lock { amount: f64, after_secs: i64 },
/// Claim locked Output at script address
Claim { tx_hash: String, index: u64 },
/// List all outputs locked at script address
Expand All @@ -37,19 +36,21 @@ async fn main() {
let contract = SmartContract::new(&logic, &backend);

match args.action {
ActionParams::Lock { amount } => contract
ActionParams::Lock { amount, after_secs } => contract
.hit_endpoint(TimeLockedEndpoints::Lock {
amount: (amount * 1_000_000.) as u64,
// TODO
timestamp: 0,
after_secs,
})
.await
.unwrap(),
ActionParams::Claim { tx_hash, index } => {
let tx_hash_bytes = hex::decode(tx_hash).unwrap();
let output_id = OutputId::new(tx_hash_bytes, index);
let endpoint = TimeLockedEndpoints::Claim { output_id };
contract.hit_endpoint(endpoint).await.unwrap()
match contract.hit_endpoint(endpoint).await {
Ok(_) => println!("Claimed output :)"),
Err(e) => println!("Error claiming output: {:?}", e),
}
}
ActionParams::List { count } => {
let res = contract
Expand All @@ -61,7 +62,11 @@ async fn main() {
println!("Active contracts:");
for output in outputs {
println!("-------------------------------------");
println!("{:?}", output.id());
println!(
"hash: {:?}, index: {:?}",
hex::encode(output.id().tx_hash()),
output.id().index()
);
println!("{:?}", output.values());
println!("{:?}", output.datum());
}
Expand Down
Loading