diff --git a/test/check/configuration/construction/transfer-configuration.json b/test/check/configuration/construction/transfer-configuration.json new file mode 100644 index 000000000..89a9f7e4f --- /dev/null +++ b/test/check/configuration/construction/transfer-configuration.json @@ -0,0 +1,30 @@ +{ + "online_url": "http://localhost:8081", + "network": { + "blockchain": "cardano", + "network": "testnet" + }, + "http_timeout": 300, + "max_retries": 5, + "retry_elapsed_time": 0, + "max_online_connections": 0, + "max_sync_concurrency": 0, + "tip_delay": 1800, + "log_configuration": false, + "construction": { + "max_offline_connections": 0, + "offline_url": "http://localhost:8081", + "stale_depth": 0, + "broadcast_limit": 0, + "ignore_broadcast_failures": false, + "clear_broadcasts": false, + "broadcast_behind_tip": false, + "block_broadcast_limit": 0, + "rebroadcast_all": false, + "constructor_dsl_file": "../../workflows/transfer.ros", + "end_conditions": { + "create_account": 1, + "transfer": 1 + } + } +} diff --git a/test/check/workflows/transfer.ros b/test/check/workflows/transfer.ros new file mode 100644 index 000000000..916ef0e51 --- /dev/null +++ b/test/check/workflows/transfer.ros @@ -0,0 +1,257 @@ +request_funds(1){ + find_account{ + currency = {"symbol":"ADA", "decimals":6}; + random_account = find_balance({ + "minimum_balance":{ + "value": "0", + "currency": {{currency}} + }, + "create_limit":1 + }); + }, + + // Create a separate scenario to request funds so that + // the address we are using to request funds does not + // get rolled back if funds do not yet exist. + request{ + loaded_account = find_balance({ + "account_identifier": {{random_account.account_identifier}}, + "minimum_balance":{ + "value": "1000000", + "currency": {{currency}} + }, + "require_coin":true + }); + } +} + +create_account(1){ + create{ + network = {"network":"testnet", "blockchain":"cardano"}; + key = generate_key({"curve_type": "edwards25519"}); + account = derive({ + "network_identifier": {{network}}, + "public_key": {{key.public_key}} + }); + + // If the account is not saved, the key will be lost! + save_account({ + "account_identifier": {{account.account_identifier}}, + "keypair": {{key}} + }); + } +} + +transfer(10){ + transfer_dry_run{ + transfer_dry_run.network = {"network":"testnet", "blockchain":"cardano"}; + currency = {"symbol":"ADA", "decimals":6}; + + // We set the max_fee_amount to know how much buffer we should + // leave for fee payment when selecting a sender account. + dust_amount = "600"; + max_fee_amount = "1000000"; + send_buffer = {{dust_amount}} + {{max_fee_amount}}; + + // We look for a coin of value >= the reserved_amount to create + // a transfer with change (reserved_amount is max_fee_amount + dust_amount x 2). + reserved_amount = "2400"; + sender = find_balance({ + "minimum_balance":{ + "value": {{reserved_amount}}, + "currency": {{currency}} + }, + "require_coin": true + }); + + // The amount we send to the recipient is a random value + // between the dust_amount and the value of the entire coin (minus + // the amount reserved for fee payment and covering the dust minimum + // of the change UTXO). + receivable_amount = {{sender.balance.value}} - {{send_buffer}}; + recipient_amount = random_number({ + "minimum": {{dust_amount}}, + "maximum": {{receivable_amount}} + }); + print_message({ + "recipient_amount":{{recipient_amount}} + }); + + // The change amount is what we aren't sending to the recipient + // minus the maximum fee. Don't worry, we will adjust this + // amount to avoid overpaying the fee after the dry run + // completes. + raw_change_amount = {{sender.balance.value}} - {{recipient_amount}}; + change_amount = {{raw_change_amount}} - {{max_fee_amount}}; + print_message({ + "change_amount":{{change_amount}} + }); + + // The last thing we need to do before creating the transaction + // is to find a recipient with a *types.AccountIdentifier that + // is not equal to the sender. + recipient = find_balance({ + "not_account_identifier":[{{sender.account_identifier}}], + "not_coins":[{{sender.coin}}], + "minimum_balance":{ + "value": "0", + "currency": {{currency}} + }, + "create_limit": 100, + "create_probability": 50 + }); + + sender_amount = 0 - {{sender.balance.value}}; + transfer_dry_run.confirmation_depth = "1"; + transfer_dry_run.dry_run = true; + transfer_dry_run.operations = [ + { + "operation_identifier":{"index":0}, + "type":"input", + "account":{{sender.account_identifier}}, + "amount":{"value":{{sender_amount}},"currency":{{currency}}}, + "coin_change":{"coin_action":"coin_spent", "coin_identifier":{{sender.coin}}}, + "status": "" // to be removed in 1.4.8 + }, + { + "operation_identifier":{"index":1}, + "type":"output", + "account":{{recipient.account_identifier}}, + "amount":{"value":{{recipient_amount}},"currency":{{currency}}}, + "status": "" // to be removed in 1.4.8 + }, + { + "operation_identifier":{"index":2}, + "type":"output", + "account":{{sender.account_identifier}}, + "amount":{"value":{{change_amount}},"currency":{{currency}}}, + "status": "" // to be removed in 1.4.8 + } + ]; + }, + transfer{ + // The suggested_fee is returned in the /construction/metadata + // response and saved to transfer_dry_run.suggested_fee. + suggested_fee = find_currency_amount({ + "currency":{{currency}}, + "amounts":{{transfer_dry_run.suggested_fee}} + }); + + // We can access the variables of other scenarios, so we don't + // need to recalculate raw_change_amount. + change_amount = {{raw_change_amount}} - {{suggested_fee.value}}; + transfer.network = {{transfer_dry_run.network}}; + transfer.confirmation_depth = {{transfer_dry_run.confirmation_depth}}; + transfer.operations = [ + { + "operation_identifier":{"index":0}, + "type":"input", + "account":{{sender.account_identifier}}, + "amount":{"value":{{sender_amount}},"currency":{{currency}}}, + "coin_change":{"coin_action":"coin_spent", "coin_identifier":{{sender.coin}}}, + "status": "" // to be removed in 1.4.8 + }, + { + "operation_identifier":{"index":1}, + "type":"output", + "account":{{recipient.account_identifier}}, + "amount":{"value":{{recipient_amount}},"currency":{{currency}}}, + "status": "" // to be removed in 1.4.8 + }, + { + "operation_identifier":{"index":2}, + "type":"output", + "account":{{sender.account_identifier}}, + "amount":{"value":{{change_amount}},"currency":{{currency}}}, + "status": "" // to be removed in 1.4.8 + } + ]; + } +} + +return_funds(10){ + transfer_dry_run{ + transfer_dry_run.network = {"network":"testnet", "blockchain":"cardano"}; + currency = {"symbol":"ADA", "decimals":6}; + + // We look for a sender that is able to pay the + // max_fee_amount + min_utxo size (reserved_amount is max_fee_amount + min_utxo size). + max_fee_amount = "1200"; + reserved_amount = "1800"; + sender = find_balance({ + "minimum_balance":{ + "value": {{reserved_amount}}, + "currency": {{currency}} + }, + "require_coin": true + }); + + // We send the maximum amount available to the recipient. Don't worry + // we will modify this after the dry run to make sure we don't overpay. + recipient_amount = {{sender.balance.value}} - {{max_fee_amount}}; + print_message({ + "recipient_amount":{{recipient_amount}} + }); + + // We load the recipient address from an ENV. + recipient_address = load_env("RECIPIENT"); + recipient = {"address": {{recipient_address}}}; + + sender_amount = 0 - {{sender.balance.value}}; + transfer_dry_run.confirmation_depth = "1"; + transfer_dry_run.dry_run = true; + transfer_dry_run.operations = [ + { + "operation_identifier":{"index":0}, + "type":"input", + "account":{{sender.account_identifier}}, + "amount":{"value":{{sender_amount}},"currency":{{currency}}}, + "coin_change":{"coin_action":"coin_spent", "coin_identifier":{{sender.coin}}}, + "status": "" // to be removed in 1.4.8 + }, + { + "operation_identifier":{"index":1}, + "type":"output", + "account":{{recipient}}, + "amount":{"value":{{recipient_amount}},"currency":{{currency}}}, + "status": "" // to be removed in 1.4.8 + } + ]; + }, + transfer{ + // The suggested_fee is returned in the /construction/metadata + // response and saved to transfer_dry_run.suggested_fee. + suggested_fee = find_currency_amount({ + "currency":{{currency}}, + "amounts":{{transfer_dry_run.suggested_fee}} + }); + + print_message({"Suggested fee", {{suggested_fee.value}}}) + // We calculate the recipient_amount using the new suggested_fee + // and assert that it is above the minimum UTXO size. + recipient_amount = {{sender.balance.value}} - {{suggested_fee.value}}; + dust_amount = "600"; + recipient_minus_dust = {{recipient_amount}} - {{dust_amount}}; + assert({{recipient_minus_dust}}); + + transfer.network = {{transfer_dry_run.network}}; + transfer.confirmation_depth = {{transfer_dry_run.confirmation_depth}}; + transfer.operations = [ + { + "operation_identifier":{"index":0}, + "type":"input", + "account":{{sender.account_identifier}}, + "amount":{"value":{{sender_amount}},"currency":{{currency}}}, + "coin_change":{"coin_action":"coin_spent", "coin_identifier":{{sender.coin}}}, + "status": "" // to be removed in 1.4.8 + }, + { + "operation_identifier":{"index":1}, + "type":"output", + "account":{{recipient}}, + "amount":{"value":{{recipient_amount}},"currency":{{currency}}}, + "status": "" // to be removed in 1.4.8 + } + ]; + } +} \ No newline at end of file