diff --git a/Cargo.lock b/Cargo.lock index d64a0e18..9b2dcba3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -861,14 +861,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" -[[package]] -name = "contract-types" -version = "0.1.0" -dependencies = [ - "casper-types 4.0.1", - "serde", -] - [[package]] name = "cookie" version = "0.18.0" diff --git a/Cargo.toml b/Cargo.toml index c123dd75..22c6519c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "kairos-server", "kairos-tx", "kairos-test-utils", - "kairos-contracts/contract-types", "kairos-contracts/deposit-contract-tests", ] diff --git a/kairos-contracts/Cargo.lock b/kairos-contracts/Cargo.lock index 2297b6aa..a6eb1a6d 100644 --- a/kairos-contracts/Cargo.lock +++ b/kairos-contracts/Cargo.lock @@ -90,6 +90,28 @@ dependencies = [ "wee_alloc", ] +[[package]] +name = "casper-event-standard" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3a1bdb142b4bfcdceec757422b2e292f446b72ce3613f881eb694f3925ef10" +dependencies = [ + "casper-contract", + "casper-event-standard-macro", + "casper-types", +] + +[[package]] +name = "casper-event-standard-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "485810e6c8387863a92e9b81e4e66ce290e2c96c0ad8ec4352e95128aa88900e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "casper-types" version = "4.0.1" @@ -141,16 +163,8 @@ dependencies = [ "base64 0.20.0", "bincode", "casper-contract", + "casper-event-standard", "casper-types", - "contract-types", -] - -[[package]] -name = "contract-types" -version = "0.1.0" -dependencies = [ - "casper-types", - "serde", ] [[package]] @@ -691,9 +705,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" diff --git a/kairos-contracts/Cargo.toml b/kairos-contracts/Cargo.toml index 4fdbd815..4250f8b8 100644 --- a/kairos-contracts/Cargo.toml +++ b/kairos-contracts/Cargo.toml @@ -6,7 +6,6 @@ members = [ "deposit-contracts/malicious-session", "deposit-contracts/deposit-session", "deposit-contracts/contract", - "contract-types", ] [workspace.package] diff --git a/kairos-contracts/contract-types/Cargo.toml b/kairos-contracts/contract-types/Cargo.toml deleted file mode 100644 index 895da2bd..00000000 --- a/kairos-contracts/contract-types/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "contract-types" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -serde = "1" -casper-types = {version="4.0.0", default-features=false} \ No newline at end of file diff --git a/kairos-contracts/contract-types/src/lib.rs b/kairos-contracts/contract-types/src/lib.rs deleted file mode 100644 index 6fc50b1f..00000000 --- a/kairos-contracts/contract-types/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -/* - The deposit contract requires a Casper-typed Deposit struct. - We can either keep this contract-types crate or move the deposit struct elsewhere. -*/ - -use casper_types::{Key, U512}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -pub struct Deposit { - pub account: Key, - pub amount: U512, - pub timestamp: Option, - pub processed: bool, -} diff --git a/kairos-contracts/deposit-contract-tests/tests/integration_tests.rs b/kairos-contracts/deposit-contract-tests/tests/integration_tests.rs index 735a5418..f652a076 100644 --- a/kairos-contracts/deposit-contract-tests/tests/integration_tests.rs +++ b/kairos-contracts/deposit-contract-tests/tests/integration_tests.rs @@ -44,14 +44,37 @@ mod tests { fixture.install_deposit_contract(init_admin); // try to update the admin list let new_admin_list: Vec = vec![Key::from(new_admin)]; - fixture.update_security_badges(new_admin_list.clone(), init_admin, init_admin); + fixture.update_security_badges_as_admin(new_admin_list.clone(), init_admin, init_admin); // update the admin list as the new_admin - fixture.update_security_badges(new_admin_list, new_admin, init_admin); + fixture.update_security_badges_as_admin(new_admin_list, new_admin, init_admin); // now remove the admin role from the installer and expect failure. let new_admin_list: Vec = vec![]; fixture.unauthorized_update_security_badges(new_admin_list.clone(), init_admin, init_admin); } + #[test] + fn admin_should_increase_last_processed_counter() { + let (mut fixture, installer, user) = setup(); + fixture.install_deposit_contract(installer); + + let value_before: u64 = + fixture.read_counter_value(installer, "last_processed_deposit_counter"); + assert_eq!(value_before, 0u64); + + fixture.admin_increase_last_processed_counter(installer, installer); + let value_after: u64 = + fixture.read_counter_value(installer, "last_processed_deposit_counter"); + assert_eq!(value_after, 1u64); + } + + #[test] + fn unauthorized_should_not_increase_last_processed_counter() { + let (mut fixture, installer, unauthorized_user) = setup(); + fixture.install_deposit_contract(installer); + + fixture.unauthorized_increase_last_processed_counter(unauthorized_user, installer); + } + // see malicious-session #[test] fn run_malicious_session() { diff --git a/kairos-contracts/deposit-contract-tests/tests/test_fixture/mod.rs b/kairos-contracts/deposit-contract-tests/tests/test_fixture/mod.rs index bbfa5b20..fa876c57 100644 --- a/kairos-contracts/deposit-contract-tests/tests/test_fixture/mod.rs +++ b/kairos-contracts/deposit-contract-tests/tests/test_fixture/mod.rs @@ -56,7 +56,7 @@ impl TestContext { .clone() } - pub fn contract_named_keys( + pub fn get_contract_named_key( &self, contract_name: &str, key_name: &str, @@ -115,7 +115,7 @@ impl TestContext { pub fn get_contract_purse_uref(&self, account: AccountHash) -> URef { let seed_uref: URef = *self - .contract_named_keys("kairos_deposit_contract", "kairos_deposit_purse", account) + .get_contract_named_key("kairos_deposit_contract", "kairos_deposit_purse", account) .as_uref() .unwrap(); seed_uref @@ -128,14 +128,60 @@ impl TestContext { #[allow(dead_code)] pub fn get_contract_purse_balance(&self, account: AccountHash) -> U512 { let contract_purse_uref: URef = *self - .contract_named_keys("kairos_deposit_contract", "kairos_deposit_purse", account) + .get_contract_named_key("kairos_deposit_contract", "kairos_deposit_purse", account) .as_uref() .unwrap(); self.builder.get_purse_balance(contract_purse_uref) } + // read a u64 counter from the contract named keys e.g. "last_processed_deposit_counter" + pub fn read_counter_value(&mut self, account: AccountHash, name: &str) -> u64 { + let contract_hash = self.contract_hash("kairos_deposit_contract", account); + self.builder.get_value(contract_hash, name) + } + + // call the contract as admin to increase the last processed deposit counter value by 1 + pub fn admin_increase_last_processed_counter( + &mut self, + caller: AccountHash, + installer: AccountHash, + ) { + let session_args = runtime_args! {}; + let update_counter_request = ExecuteRequestBuilder::contract_call_by_hash( + caller, + self.contract_hash("kairos_deposit_contract", installer), + "incr_last_processed_deposit_counter", + session_args, + ) + .build(); + self.builder + .exec(update_counter_request) + .expect_success() + .commit(); + } + + // an unauthorized attempt of changing the last processed counter + pub fn unauthorized_increase_last_processed_counter( + &mut self, + caller: AccountHash, + installer: AccountHash, + ) { + let session_args = runtime_args! {}; + let update_counter_request = ExecuteRequestBuilder::contract_call_by_hash( + caller, + self.contract_hash("kairos_deposit_contract", installer), + "incr_last_processed_deposit_counter", + session_args, + ) + .build(); + self.builder + .exec(update_counter_request) + .expect_failure() + .commit(); + } + // try to update the access control / admin list of the deposit contract - pub fn update_security_badges( + pub fn update_security_badges_as_admin( &mut self, admin_list: Vec, caller: AccountHash, diff --git a/kairos-contracts/deposit-contracts/contract/Cargo.toml b/kairos-contracts/deposit-contracts/contract/Cargo.toml index 4b02cea2..2d7d7c19 100644 --- a/kairos-contracts/deposit-contracts/contract/Cargo.toml +++ b/kairos-contracts/deposit-contracts/contract/Cargo.toml @@ -7,8 +7,8 @@ license.workspace = true [dependencies] casper-contract = {version="4.0.0", features=["std", "test-support"]} casper-types = {version="4.0.0", default-features=false} +casper-event-standard="0.5.0" base64 = { version = "0.20.0", default-features = false, features = ["alloc"] } -contract-types = {path="../../contract-types"} bincode = "1.3.3" @@ -24,4 +24,4 @@ test = false [profile.release] codegen-units = 1 -lto = true \ No newline at end of file +lto = true diff --git a/kairos-contracts/deposit-contracts/contract/src/constants.rs b/kairos-contracts/deposit-contracts/contract/src/constants.rs index 19d64b92..588523c3 100644 --- a/kairos-contracts/deposit-contracts/contract/src/constants.rs +++ b/kairos-contracts/deposit-contracts/contract/src/constants.rs @@ -4,8 +4,6 @@ pub const KAIROS_DEPOSIT_CONTRACT_NAME: &str = "kairos_deposit_contract"; pub const KAIROS_DEPOSIT_CONTRACT_PACKAGE: &str = "deposit_contract_package"; pub const KAIROS_DEPOSIT_CONTRACT: &str = "deposit_contract"; -pub const KAIROS_DEPOSIT_EVENT_DICT: &str = "kairos_deposit_event_dict"; -pub const KAIROS_MOST_RECENT_DEPOSIT_COUNTER: &str = "most_recent_deposit_counter"; pub const KAIROS_LAST_PROCESSED_DEPOSIT_COUNTER: &str = "last_processed_deposit_counter"; pub const KAIROS_DEPOSIT_PURSE: &str = "kairos_deposit_purse"; diff --git a/kairos-contracts/deposit-contracts/contract/src/events.rs b/kairos-contracts/deposit-contracts/contract/src/events.rs new file mode 100644 index 00000000..4ce06cdc --- /dev/null +++ b/kairos-contracts/deposit-contracts/contract/src/events.rs @@ -0,0 +1,11 @@ +use casper_event_standard::Event; +extern crate alloc; +use alloc::{string::String, vec::Vec}; +use casper_types::{Key, U512}; + +#[derive(Event)] +pub struct Deposit { + pub account: Key, + pub amount: U512, + pub timestamp: Option, +} diff --git a/kairos-contracts/deposit-contracts/contract/src/main.rs b/kairos-contracts/deposit-contracts/contract/src/main.rs index 4f0e3c79..6fbdaf93 100644 --- a/kairos-contracts/deposit-contracts/contract/src/main.rs +++ b/kairos-contracts/deposit-contracts/contract/src/main.rs @@ -12,10 +12,9 @@ use casper_types::{ }; mod constants; use constants::{ - ADMIN_LIST, KAIROS_DEPOSIT_CONTRACT, KAIROS_DEPOSIT_CONTRACT_NAME, KAIROS_DEPOSIT_EVENT_DICT, - KAIROS_DEPOSIT_PURSE, KAIROS_LAST_PROCESSED_DEPOSIT_COUNTER, - KAIROS_MOST_RECENT_DEPOSIT_COUNTER, RUNTIME_ARG_AMOUNT, RUNTIME_ARG_TEMP_PURSE, - SECURITY_BADGES, + ADMIN_LIST, KAIROS_DEPOSIT_CONTRACT, KAIROS_DEPOSIT_CONTRACT_NAME, + KAIROS_DEPOSIT_CONTRACT_PACKAGE, KAIROS_DEPOSIT_PURSE, KAIROS_LAST_PROCESSED_DEPOSIT_COUNTER, + RUNTIME_ARG_AMOUNT, RUNTIME_ARG_TEMP_PURSE, SECURITY_BADGES, }; mod utils; use utils::{get_immediate_caller, get_optional_named_arg_with_user_errors}; @@ -24,8 +23,10 @@ use error::DepositError; mod security; use security::{access_control_check, SecurityBadge}; mod entry_points; - -use contract_types::Deposit; +mod events; +use casper_event_standard; +use casper_event_standard::Schemas; +use events::Deposit; // This entry point is called once when the contract is installed // and sets up the security badges with the installer as an admin or the @@ -42,6 +43,10 @@ pub extern "C" fn init() { .unwrap_or_revert_with(DepositError::FailedToCreateSecurityBadgesDict); let installing_entity = runtime::get_caller(); + // initialize event schema + let schemas = Schemas::new().with::(); + casper_event_standard::init(schemas); + // Assign the admin role to the installer, regardless of the list of admins that was // passed to the installation session. // The installer is by default an admin and the installer's admin role @@ -96,38 +101,12 @@ pub extern "C" fn deposit() { system::transfer_from_purse_to_purse(temp_purse, deposit_purse_uref, amount, None) .unwrap_or_revert(); - let most_recent_deposit_counter_uref = runtime::get_key(KAIROS_MOST_RECENT_DEPOSIT_COUNTER) - .unwrap_or_revert_with(DepositError::MissingKeyMostRecentDepositCounter) - .into_uref() - .unwrap_or_revert(); - let mut most_recent_deposit_counter_value: u64 = - storage::read(most_recent_deposit_counter_uref) - .unwrap_or_revert() - .unwrap_or_revert(); let new_deposit_record: Deposit = Deposit { account: get_immediate_caller().unwrap_or_revert(), - amount, + amount: amount, timestamp: None, - processed: false, }; - - let deposit_event_dict_key: &str = &most_recent_deposit_counter_value.to_string(); - - let kairos_deposit_event_dict_uref = runtime::get_key(KAIROS_DEPOSIT_EVENT_DICT) - .unwrap_or_revert_with(DepositError::MissingKeyDepositEventDict) - .into_uref() - .unwrap_or_revert(); - storage::dictionary_put::>( - kairos_deposit_event_dict_uref, - deposit_event_dict_key, - bincode::serialize(&new_deposit_record).unwrap(), - ); - - most_recent_deposit_counter_value += 1u64; - storage::write( - most_recent_deposit_counter_uref, - most_recent_deposit_counter_value, - ); + casper_event_standard::emit(new_deposit_record); } // The centralized Kairos service, or a sequencer, @@ -192,9 +171,6 @@ pub extern "C" fn call() { entry_points }; let mut named_keys = NamedKeys::new(); - let event_dict = storage::new_dictionary(KAIROS_DEPOSIT_EVENT_DICT) - .unwrap_or_revert_with(DepositError::FailedToCreateDepositDict); - named_keys.insert(KAIROS_DEPOSIT_EVENT_DICT.to_string(), event_dict.into()); let last_processed_deposit_counter = storage::new_uref(u64::from(0u8)); named_keys.insert( @@ -202,18 +178,11 @@ pub extern "C" fn call() { last_processed_deposit_counter.into(), ); - let most_recent_deposit_counter = storage::new_uref(u64::from(0u8)); - named_keys.insert( - KAIROS_MOST_RECENT_DEPOSIT_COUNTER.to_string(), - most_recent_deposit_counter.into(), - ); - - let (contract_hash, _) = storage::new_contract( + let (contract_hash, _) = storage::new_locked_contract( entry_points, Some(named_keys), Some(KAIROS_DEPOSIT_CONTRACT.to_string()), - // Some(key) if upgradable - None, + Some(KAIROS_DEPOSIT_CONTRACT_PACKAGE.to_string()), ); let contract_hash_key = Key::from(contract_hash); runtime::put_key(KAIROS_DEPOSIT_CONTRACT_NAME, contract_hash_key); diff --git a/kairos-server/src/routes/mod.rs b/kairos-server/src/routes/mod.rs index cc2b615e..9d4eb45c 100644 --- a/kairos-server/src/routes/mod.rs +++ b/kairos-server/src/routes/mod.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct PayloadBody { + #[serde(deserialize_with = "hex_to_vec", serialize_with = "vec_to_hex")] pub public_key: PublicKey, #[serde(deserialize_with = "hex_to_vec", serialize_with = "vec_to_hex")] pub payload: Vec, diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index 40a76c98..4a60d7ea 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -23,6 +23,8 @@ nixosTest { }; services.cctl.enable = true; environment.systemPackages = [ casper-client-rs ]; + # allow HTTP for nixos-test environment + services.nginx.virtualHosts.${config.networking.hostName}.forceSSL = lib.mkForce false; }; client = { config, pkgs, nodes, ... }: { @@ -35,53 +37,73 @@ nixosTest { cctlUsersDirectory = "/var/lib/cctl/assets/users"; in '' - import json - - start_all() - - kairos.wait_for_unit("cctl.service") - - kairos.wait_for_unit("kairos.service") - kairos.wait_for_unit("nginx.service") - kairos.wait_for_open_port(80) - - client.wait_for_unit ("multi-user.target") - - kairos.succeed("casper-client get-node-status --node-address http://localhost:11101") - - # chain-name see: https://github.com/casper-network/cctl/blob/745155d080934c409d98266f912b8fd2b7e28a00/utils/constants.sh#L66 - # port see: https://github.com/casper-network/cctl/blob/745155d080934c409d98266f912b8fd2b7e28a00/utils/constants.sh#L13 - kairos.succeed("casper-client put-deploy --node-address http://localhost:11101 --chain-name cspr-dev-cctl --secret-key $(ls ${cctlUsersDirectory}/user-1/secret_key.pem) --payment-amount 5000000000000 --session-path ${kairos-contracts}/bin/deposit-contract-optimized.wasm") - - # REST API - deposit_request = { "public_key": "publickey", "amount": 10 } - client.succeed("curl -X POST http://kairos/api/v1/deposit -H 'Content-Type: application/json' -d '{}'".format(json.dumps(deposit_request))) - - transfer_request = { "from": "publickey", "signature": "signature", "to": "publickey", "amount": 10 } - client.succeed("curl -X POST http://kairos/api/v1/transfer -H 'Content-Type: application/json' -d '{}'".format(json.dumps(transfer_request))) - - withdraw_request = { "public_key": "publickey", "signature": "signature", "amount": 10 } - client.succeed("curl -X POST http://kairos/api/v1/withdraw -H 'Content-Type: application/json' -d '{}'".format(json.dumps(withdraw_request))) - - # CLI with ed25519 - cli_output = client.succeed("kairos-cli deposit --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") - assert "ok\n" in cli_output - - cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") - assert "ok\n" in cli_output - - cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") - assert "ok\n" in cli_output - - # CLI with secp256k1 - cli_output = client.succeed("kairos-cli deposit --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") - assert "ok\n" in cli_output - - cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") - assert "ok\n" in cli_output - - cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") - assert "ok\n" in cli_output + import json + + start_all() + + kairos.wait_for_unit("cctl.service") + + kairos.wait_for_unit("kairos.service") + kairos.wait_for_unit("nginx.service") + kairos.wait_for_open_port(80) + + client.wait_for_unit ("multi-user.target") + + kairos.succeed("casper-client get-node-status --node-address http://localhost:11101") + + # chain-name see: https://github.com/casper-network/cctl/blob/745155d080934c409d98266f912b8fd2b7e28a00/utils/constants.sh#L66 + # port see: https://github.com/casper-network/cctl/blob/745155d080934c409d98266f912b8fd2b7e28a00/utils/constants.sh#L13 + kairos.succeed("casper-client put-deploy --node-address http://localhost:11101 --chain-name cspr-dev-cctl --secret-key $(ls ${cctlUsersDirectory}/user-1/secret_key.pem) --payment-amount 5000000000000 --session-path ${kairos-contracts}/bin/deposit-contract-optimized.wasm") + + # REST API + # Tx Payload + # nonce = 1 + # deposit: + # amount = 1000 + # + deposit_payload = "3009020101a004020203e8" + deposit_request = { "public_key": "deadbeef", "payload": deposit_payload, "signature": "cafebabe" } + # REST API + client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/deposit -H 'Content-Type: application/json' -d '{}'".format(json.dumps(deposit_request))) + + # Tx Payload + # nonce = 2 + # transfer: + # recipient = DEADBEEF + # amount = 1000 + # + transfer_payload = "300f020102a10a0404deadbeef020203e8" + transfer_request = { "public_key": "deadbeef", "payload": transfer_payload, "signature": "cafebabe" } + client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/transfer -H 'Content-Type: application/json' -d '{}'".format(json.dumps(transfer_request))) + + # Tx Payload + # nonce = 3 + # withdrawal: + # amount = 1000 + # + withdraw_payload = "3009020103a204020203e8" + withdraw_request = { "public_key": "deadbeef", "payload": withdraw_payload, "signature": "cafebabe" } + client.succeed("curl --fail-with-body -X POST http://kairos/api/v1/withdraw -H 'Content-Type: application/json' -d '{}'".format(json.dumps(withdraw_request))) + + # CLI with ed25519 + cli_output = client.succeed("kairos-cli deposit --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") + assert "ok\n" in cli_output + + cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") + assert "ok\n" in cli_output + + cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/ed25519/secret_key.pem") + assert "ok\n" in cli_output + + # CLI with secp256k1 + cli_output = client.succeed("kairos-cli deposit --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") + assert "ok\n" in cli_output + + cli_output = client.succeed("kairos-cli transfer --recipient '01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356' --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") + assert "ok\n" in cli_output + + cli_output = client.succeed("kairos-cli withdraw --amount 1000 --private-key ${testResources}/secp256k1/secret_key.pem") + assert "ok\n" in cli_output ''; }